Як нарізати масив у Bash


197

Переглядаючи розділ "Масив" на сторінці "bash (1)", я не знайшов способу нарізати масив.

Тому я придумав цю надмірно складну функцію:

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

Використовується так:

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

Чи є кращий спосіб зробити це?


Я шукав, як відрізати кінець масиву, і був направлений сюди. Відповідь не знайдено тут, і це буде дублікат, тому що я знайшов відповідь тут stackoverflow.com/questions/44939747/… . Основна ідея полягає в тому, що ми можемо мати арифметичний вираз, такий як $ {# array [@]} - (2 + 7), де довжина очікується в конструкції $ {array: offset: length}. Жодна із наданих тут відповідей не ілюструє це.
Dominic108

Відповіді:


313

Дивіться розділ Розширення параметрів на manсторінці Bash . A[@]повертає вміст масиву, :1:2займає фрагмент довжиною 2, починаючи з індексу 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

Зауважимо, що факт "ab c" є одним елементом масиву (і що він містить додатковий простір) зберігається.


2
Класно. Я заглянув у розділ «Масив» і там його не побачив.
Чен Леві

36
Це нерозумно Чен, чому це було б у розділі "Масив"? * sarc
deltaray

1
@AquariusPower: Створити масив індексів і нарізати його: idx=(${!A[@]}); echo ${idx[@]:1}.
Призупинено до подальшого повідомлення.

7
@Feuermurmel: Просто зробіть це без індексації квадратних дужок:${@:1:2}
Призупинено до подальшого повідомлення.

5
@DennisWilliamson Я виявив, що мені потрібно перетворитись $@на належний масив, перш ніж робити це, або аргументи, що містять пробіли, розколються:ARGS=( "$@" ); ARGS_AFTER_FIRST=( "${ARGS[@]:1}" )
Heath Borders

47

Також є зручний ярлик для отримання всіх елементів масиву, починаючи з вказаного індексу. Наприклад, "$ {A [@]: 1}" був би "хвостом" масиву, тобто масивом без першого елемента.

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

8
І поки ви на це:echo "${A[@]::1}" # 4
Чен Леві

7
Це чудово, але слід зазначити, що якщо він використовується в межах функції, його слід трохи змінити, щоб прочитати "${${@}[@]:1}".
Алекс Грей

@AlexGray: Це дає мені "погану заміну" тут, але ${@:2}працює чудово.
Нік Маттео

3

Нарізка масиву, як у Python (З бібліотеки перезаписів ):

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

Скажімо, я читаю масив від користувача, тоді я хочу бачити елемент 3 до 7 як включно.

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


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

4
Синтаксис фрагмента у вашому коді ідентичний тому, що у 8-річній прийнятій відповіді. Ваша відповідь нічого нового не додає.
Мельпомена
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.