查看bash(1)手册页中的“Array”部分,我没有找到对数组进行切片的方法。
所以我想出了这个过于复杂的函数:
#!/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
还有更好的办法吗?
像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"
冒着白费口舌的风险,我从@jandob的回答中受到启发,制作了这个版本
更简单(没有那么多移位逻辑或经常重写变量)。
尊重引用字符串而不处理IFS(仅限-r模式)。
允许用户通过-l标记指定[start, end]切片或[start, length]切片。
允许你回显结果数组(默认行为),或者“返回”它到一个新的数组中用于调用父数组(通过-r slicedArray)。
注意:namerefs仅在Bash >= 4.3中支持。为了支持早期版本的Bash(即Mac没有Brew的Bash),你需要使用间接方式:使用临时变量来访问数组参数,例如声明arrValuesCmd="$1[@]";declare arr=("${!arrValuesCmd}"),并使用eval作为返回值,例如eval $retArrName='("${newArr[@]}")'(注意数组声明周围的单引号)。
array.slice() {
# array.slice [-l] [-r returnArrayName] myArray 3 5
# Default functionality is to use second number as end index for slice (exclusive).
# Can instead use second number as length by passing `-l` flag.
# `echo` doesn't maintain quoted entries, so pass in `-r returnArrayName` to keep them.
declare isLength
declare retArrName
declare OPTIND=1
while getopts "lr:" opt; do
case "$opt" in
l)
# If `end` is slice length instead of end index
isLength=true
;;
r)
retArrName="$OPTARG"
;;
esac
done
shift $(( OPTIND - 1 ))
declare -n arr="$1"
declare start="$2"
declare end="$3"
declare arrLength="${#arr[@]}"
declare newArr=()
declare newArrLength
# Bash native slicing:
# Positive index values: ${array:start:length}
# Negative index values: ${array: start: length}
# To use negative values, a space is required between `:` and the variable
# because `${var:-3}` actually represents a default value,
# e.g. `myVar=${otherVal:-7}` represents (pseudo-code) `myVar=otherVal || myVar=7`
if [[ -z "$end" ]]; then
# If no end is specified (regardless of `-l`/length or index), default to the rest of the array
newArrLength="$arrLength"
elif [[ -n "$isLength" ]]; then
# If specifying length instead of end-index, use native bash array slicing
newArrLength="$(( end ))"
else
# If specifying end-index, use custom slicing based on a range of [start, end):
newArrLength="$(( end - start ))"
fi
newArr=("${arr[@]: start: newArrLength}")
if [[ -n "$retArrName" ]]; then
declare -n retArr="$retArrName"
retArr=("${newArr[@]}")
else
echo "${newArr[@]}"
fi
}
例子:
myArray=(x y 'a b c' z 5 14) # length=6
array.slice myArray 2 4
# > a b c z
array.slice -l myArray 3 2
# > z 5
# Note: Output was manually quoted to show the result more clearly.
# Actual stdout content won't contain those quotes, which is
# why the `-r returnArray` option was added.
array.slice -r slicedArray myArray -5 -3 # equivalent of [2, 4)
# > (null)
echo -e "myArray (length=${#myArray[@]}): ${myArray[@]} \nslicedArray (length=${#slicedArray[@]}): ${slicedArray[@]}"
# > myArray (length=6): x y 'a b c' z 5 14
# > slicedArray (length=2): 'a b c' z
array.slice -lr slicedArray myArray -5 3 # length instead of index, equivalent of [2, 5)
# > (null)
echo -e "myArray (length=${#myArray[@]}): ${myArray[@]} \nslicedArray (length=${#slicedArray[@]}): ${slicedArray[@]}"
# > myArray (length=6): x y 'a b c' z 5 14
# > slicedArray (length=3): 'a b c' z 5