如何在Bash中切片数组


194

查看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

Answers:


310

请参见Bash 页面中的“ 参数扩展”部分manA[@]返回数组的内容:1:2,从索引1开始,取长度为2的切片。

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
凉。我查看了“数组”部分,但没有在那儿看到。
陈征费

35
那真是可笑的陈,为什么会出现在“数组”部分中?* sarc
deltaray 2014年

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
Chen Levy

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
您代码中的slice语法与8年前接受的答案中的语法相同。您的答案没有新内容。
melpomene
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.