Jak pokroić tablicę w Bash


196

Przeglądając sekcję „Tablica” na stronie podręcznika bash (1), nie znalazłem sposobu na wycięcie tablicy.

Więc wymyśliłem tę zbyt skomplikowaną funkcję:

#!/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 )"
}

Używany w ten sposób:

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

Czy jest na to lepszy sposób?


Szukałem sposobu odcięcia końca tablicy i skierowano mnie tutaj. Odpowiedź nie została tutaj znaleziona i będzie to duplikat, ponieważ znalazłem odpowiedź tutaj stackoverflow.com/questions/44939747/… . Podstawową ideą jest to, że możemy mieć wyrażenie arytmetyczne, takie jak $ {# tablica [@]} - (2 + 7), gdzie długość jest oczekiwana w konstrukcji $ {array: offset: length}. Żadna z podanych tutaj odpowiedzi tego nie ilustruje.
Dominic108,

Odpowiedzi:


312

Zobacz sekcję Rozszerzanie parametrów na manstronie Bash . A[@]zwraca zawartość tablicy, :1:2pobiera plasterek o długości 2, zaczynając od indeksu 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

Zauważ, że fakt, że „ab c” jest jednym elementem tablicy (i zawiera dodatkową spację) zostaje zachowany.


2
Chłodny. Zajrzałem do sekcji Szyk i tam jej nie widziałem.
Chen Levy

36
To głupie Chen, dlaczego miałoby być w sekcji Szyk? * sarc
deltaray

1
@AquariusPower: Utwórz tablicę wskaźników i pokroić go: idx=(${!A[@]}); echo ${idx[@]:1}.
Wstrzymano do odwołania.

7
@ Feuermurmel: Po prostu zrób to bez kwadratowych nawiasów indeksujących:${@:1:2}
Wstrzymano do odwołania.

5
@DennisWilliamson Stwierdziłem, że muszę to przekonwertować $@na odpowiednią tablicę, zanim to zrobię, lub argumenty zawierające spacje zostaną podzielone:ARGS=( "$@" ); ARGS_AFTER_FIRST=( "${ARGS[@]:1}" )
Heath Borders

47

Istnieje również wygodny skrót, aby uzyskać wszystkie elementy tablicy, zaczynając od określonego indeksu. Na przykład „$ {A [@]: 1}” byłoby „ogonem” tablicy, czyli tablicą bez pierwszego elementu.

version=4.7.1
A=( ${version//\./ } )
echo "${A[@]}"    # 4 7 1
B=( "${A[@]:1}" )
echo "${B[@]}"    # 7 1

8
I podczas gdy ty:echo "${A[@]::1}" # 4
Chen Levy,

7
Jest to świetne, ale należy zauważyć, że jeśli jest używane w ramach funkcji, należy ją nieco zmienić, aby odczytać "${${@}[@]:1}".
Alex Gray

@AlexGray: To daje mi tutaj „złe zastąpienie”, ale ${@:2}działa dobrze.
Nick Matteo

3

Wycinanie tablic jak w Pythonie (z biblioteki rebash ):

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"

1

Powiedzmy, że czytam tablicę od użytkownika, a następnie chcę zobaczyć elementy 3–7 łącznie.

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


echo ${myarr[@]:3:5}

4
Składnia wycinka w kodzie jest identyczna jak w 8-letniej akceptowanej odpowiedzi. Twoja odpowiedź nie dodaje nic nowego.
melpomene
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.