Как нарезать массив в 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
Прохладно. Я посмотрел в разделе Array и не увидел его там.
Чен Леви

36
Это глупо, Чен, с чего бы это в разделе «Массив»? * sarc
deltaray

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

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

5
@DennisWilliamson Я обнаружил, что мне нужно преобразовать $@в правильный массив, прежде чем делать это, иначе аргументы, содержащие пробелы, будут разделены:ARGS=( "$@" ); ARGS_AFTER_FIRST=( "${ARGS[@]:1}" )
Хит Границы

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 (из библиотеки 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

Допустим, я читаю массив от пользователя, затем я хочу видеть элементы с 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.