Cách cắt một mảng trong Bash


194

Nhìn vào phần "Mảng" trong trang bash (1), tôi không tìm thấy cách nào để cắt một mảng.

Vì vậy, tôi đã đưa ra chức năng quá phức tạp này:

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

Được sử dụng như thế này:

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

Có cách nào tốt hơn để làm điều này?


Tôi đã tìm kiếm làm thế nào để cắt bỏ phần cuối của một mảng và được hướng dẫn ở đây. Câu trả lời không được tìm thấy ở đây và nó sẽ là một bản sao để làm như vậy, bởi vì tôi đã tìm thấy câu trả lời ở đây stackoverflow.com/questions/44939747/ trên . Ý tưởng cơ bản là chúng ta có thể có một biểu thức số học, chẳng hạn như $ {# mảng [@]} - (2 + 7) trong đó độ dài được mong đợi trong cấu trúc $ {mảng: offset: length}. Không có câu trả lời nào được cung cấp ở đây minh họa điều đó.
Đaminh 108

Câu trả lời:


310

Xem phần Mở rộng tham số trong mantrang Bash . A[@]trả về nội dung của mảng, :1:2lấy một lát có độ dài 2, bắt đầu từ chỉ mục 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

Lưu ý rằng thực tế là "ab c" là một phần tử mảng (và nó chứa một khoảng trắng thừa) được giữ nguyên.


2
Mát mẻ. Tôi đã xem trong phần Array và không thấy nó ở đó.
Chen Levy

36
Đó là Chen ngớ ngẩn, tại sao nó lại nằm trong phần Array? * mỉa mai
deltaray

1
@AquariusPower: Tạo một loạt các chỉ mục và cắt nó : idx=(${!A[@]}); echo ${idx[@]:1}.
Tạm dừng cho đến khi có thông báo mới.

7
@Feuermurmel: Chỉ cần làm điều đó mà không có dấu ngoặc vuông lập chỉ mục:${@:1:2}
Tạm dừng cho đến khi có thông báo mới.

5
@DennisWilliamson Tôi thấy rằng tôi cần phải chuyển đổi $@thành một mảng thích hợp trước khi thực hiện điều này hoặc các đối số có khoảng trắng sẽ bị chia tách:ARGS=( "$@" ); ARGS_AFTER_FIRST=( "${ARGS[@]:1}" )
Heath

47

Ngoài ra còn có một phím tắt thuận tiện để có được tất cả các yếu tố của mảng bắt đầu với chỉ mục được chỉ định. Ví dụ: "$ {A [@]: 1}" sẽ là "đuôi" của mảng, đó là mảng không có phần tử đầu tiên.

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

8
Và trong khi bạn ở đó:echo "${A[@]::1}" # 4
Chen Levy

7
Điều này thật tuyệt, nhưng cần lưu ý rằng nếu được sử dụng trong một hàm, nó phải được thay đổi một chút để đọc "${${@}[@]:1}".
Alex Gray

@AlexGray: Điều đó mang lại cho tôi "sự thay thế tồi" ở đây, nhưng ${@:2}hoạt động tốt.
Nick Matteo

3

Cắt mảng như trong Python (Từ thư viện 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

Hãy nói rằng tôi đang đọc một mảng từ người dùng, sau đó tôi muốn xem cả phần tử 3 đến 7.

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


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

4
Cú pháp lát trong mã của bạn giống hệt với câu trả lời được chấp nhận trong 8 năm. Câu trả lời của bạn không có gì mới.
melpomene
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.