So schneiden Sie ein Array in Bash

194

Im Abschnitt "Array" in der Bash (1) -Manpage habe ich keine Möglichkeit gefunden, ein Array in Scheiben zu schneiden.

Also habe ich mir diese übermäßig komplizierte Funktion ausgedacht:

#!/bin/bash

# @brief: slice a bash array
# @arg1:  output-name
# @arg2:  input-name
# @args:  seq args
# ----------------------------------------------
function slice() {
   local output=$1
   local input=$2
   shift 2
   local indexes=$(seq $*)

   local -i i
   local tmp=$(for i in $indexes 
                 do echo "$(eval echo \"\${$input[$i]}\")" 
               done)

   local IFS=$'\n'
   eval $output="( \$tmp )"
}

So verwendet:

$ A=( foo bar "a  b c" 42 )
$ slice B A 1 2
$ echo "${B[0]}"  # bar
$ echo "${B[1]}"  # a  b c

Gibt es einen besseren Weg, dies zu tun?

Chen Levy
quelle
Ich suchte, wie man das Ende eines Arrays abschneidet, und wurde hierher geleitet. Die Antwort wird hier nicht gefunden und es wird ein Duplikat sein, da ich die Antwort hier gefunden habe stackoverflow.com/questions/44939747/… . Die Grundidee ist, dass wir einen arithmetischen Ausdruck wie $ {# array [@]} - (2 + 7) haben können, wobei die Länge im Konstrukt $ {array: offset: length} erwartet wird. Keine der hier gegebenen Antworten veranschaulicht dies.
Dominic108

Antworten:

310

Siehe den Abschnitt Parametererweiterung auf der manSeite Bash . A[@]Gibt den Inhalt des Arrays zurück und :1:2nimmt ein Segment der Länge 2, beginnend mit Index 1.

A=( foo bar "a  b c" 42 )
B=("${A[@]:1:2}")
C=("${A[@]:1}")       # slice to the end of the array
echo "${B[@]}"        # bar a  b c
echo "${B[1]}"        # a  b c
echo "${C[@]}"        # bar a  b c 42
echo "${C[@]: -2:2}"  # a  b c 42 # The space before the - is necesssary

Beachten Sie, dass die Tatsache, dass "ab c" ein Array-Element ist (und einen zusätzlichen Speicherplatz enthält), erhalten bleibt.

Bis auf weiteres angehalten.
quelle
2
Cool. Ich habe im Array-Bereich nachgesehen und ihn dort nicht gesehen.
Chen Levy
35
Das ist albern Chen, warum sollte es in der Array-Sektion sein? * Sarc
Deltaray
1
@AquariusPower: Erstellen Sie ein Array von Indizes und schneiden Sie es in Scheiben : idx=(${!A[@]}); echo ${idx[@]:1}.
Bis auf weiteres angehalten.
7
@Feuermurmel: Mach es einfach ohne die eckigen ${@:1:2}
Indexklammern
5
@ TennisWilliamson Ich fand, dass ich $@in ein richtiges Array konvertieren musste , bevor ich dies tat, oder Argumente, die Leerzeichen enthielten, wurden aufgeteilt:ARGS=( "$@" ); ARGS_AFTER_FIRST=( "${ARGS[@]:1}" )
Heath Borders
47

Es gibt auch eine praktische Verknüpfung, mit der alle Elemente des Arrays ab dem angegebenen Index abgerufen werden können. Zum Beispiel wäre "$ {A [@]: 1}" das "Ende" des Arrays, dh das Array ohne sein erstes Element.

version=4.7.1
A=( ${version//\./ } )
echo "${A[@]}"    # 4 7 1
B=( "${A[@]:1}" )
echo "${B[@]}"    # 7 1
Nicholas Sushkin
quelle
8
Und während Sie dabei sind:echo "${A[@]::1}" # 4
Chen Levy
7
Das ist großartig, aber es sollte beachtet werden, dass wenn es innerhalb einer Funktion verwendet wird, es zum Lesen leicht geändert werden muss "${${@}[@]:1}".
Alex Gray
@AlexGray: Das gibt mir hier "schlechte Substitution", ${@:2}funktioniert aber gut.
Nick Matteo
3

Array-Slicing wie in Python (aus der Rebash- Bibliothek):

array_slice() {
    local __doc__='
    Returns a slice of an array (similar to Python).

    From the Python documentation:
    One way to remember how slices work is to think of the indices as pointing
    between elements, with the left edge of the first character numbered 0.
    Then the right edge of the last element of an array of length n has
    index n, for example:
    ```
    +---+---+---+---+---+---+
    | 0 | 1 | 2 | 3 | 4 | 5 |
    +---+---+---+---+---+---+
    0   1   2   3   4   5   6
    -6  -5  -4  -3  -2  -1
    ```

    >>> local a=(0 1 2 3 4 5)
    >>> echo $(array.slice 1:-2 "${a[@]}")
    1 2 3
    >>> local a=(0 1 2 3 4 5)
    >>> echo $(array.slice 0:1 "${a[@]}")
    0
    >>> local a=(0 1 2 3 4 5)
    >>> [ -z "$(array.slice 1:1 "${a[@]}")" ] && echo empty
    empty
    >>> local a=(0 1 2 3 4 5)
    >>> [ -z "$(array.slice 2:1 "${a[@]}")" ] && echo empty
    empty
    >>> local a=(0 1 2 3 4 5)
    >>> [ -z "$(array.slice -2:-3 "${a[@]}")" ] && echo empty
    empty
    >>> [ -z "$(array.slice -2:-2 "${a[@]}")" ] && echo empty
    empty

    Slice indices have useful defaults; an omitted first index defaults to
    zero, an omitted second index defaults to the size of the string being
    sliced.
    >>> local a=(0 1 2 3 4 5)
    >>> # from the beginning to position 2 (excluded)
    >>> echo $(array.slice 0:2 "${a[@]}")
    >>> echo $(array.slice :2 "${a[@]}")
    0 1
    0 1

    >>> local a=(0 1 2 3 4 5)
    >>> # from position 3 (included) to the end
    >>> echo $(array.slice 3:"${#a[@]}" "${a[@]}")
    >>> echo $(array.slice 3: "${a[@]}")
    3 4 5
    3 4 5

    >>> local a=(0 1 2 3 4 5)
    >>> # from the second-last (included) to the end
    >>> echo $(array.slice -2:"${#a[@]}" "${a[@]}")
    >>> echo $(array.slice -2: "${a[@]}")
    4 5
    4 5

    >>> local a=(0 1 2 3 4 5)
    >>> echo $(array.slice -4:-2 "${a[@]}")
    2 3

    If no range is given, it works like normal array indices.
    >>> local a=(0 1 2 3 4 5)
    >>> echo $(array.slice -1 "${a[@]}")
    5
    >>> local a=(0 1 2 3 4 5)
    >>> echo $(array.slice -2 "${a[@]}")
    4
    >>> local a=(0 1 2 3 4 5)
    >>> echo $(array.slice 0 "${a[@]}")
    0
    >>> local a=(0 1 2 3 4 5)
    >>> echo $(array.slice 1 "${a[@]}")
    1
    >>> local a=(0 1 2 3 4 5)
    >>> array.slice 6 "${a[@]}"; echo $?
    1
    >>> local a=(0 1 2 3 4 5)
    >>> array.slice -7 "${a[@]}"; echo $?
    1
    '
    local start end array_length length
    if [[ $1 == *:* ]]; then
        IFS=":"; read -r start end <<<"$1"
        shift
        array_length="$#"
        # defaults
        [ -z "$end" ] && end=$array_length
        [ -z "$start" ] && start=0
        (( start < 0 )) && let "start=(( array_length + start ))"
        (( end < 0 )) && let "end=(( array_length + end ))"
    else
        start="$1"
        shift
        array_length="$#"
        (( start < 0 )) && let "start=(( array_length + start ))"
        let "end=(( start + 1 ))"
    fi
    let "length=(( end - start ))"
    (( start < 0 )) && return 1
    # check bounds
    (( length < 0 )) && return 1
    (( start < 0 )) && return 1
    (( start >= array_length )) && return 1
    # parameters start with $1, so add 1 to $start
    let "start=(( start + 1 ))"
    echo "${@: $start:$length}"
}
alias array.slice="array_slice"
Jandob
quelle
1

Nehmen wir an, ich lese ein Array vom Benutzer, dann möchte ich die Elemente 3 bis 7 einschließlich sehen.

cnt=0
while read var;
    do
    myarr[cnt]=$var
    cnt=$((cnt+1)) 
    done


echo ${myarr[@]:3:5}
Arindam Roychowdhury
quelle
4
Die Slice-Syntax in Ihrem Code ist identisch mit der in der 8 Jahre alten akzeptierten Antwort. Ihre Antwort fügt nichts Neues hinzu.
Melpomene