学习路径
/
Bash
Bash
/
教学大纲
/
深入了解数组
深入

深入了解数组 属于 Bash

{other: "%{count} 个练习"}

关于 深入了解数组

我们在数组一章中介绍了数组。本文会展示更多使用数组的方法。

将数组的元素拼接成一个字符串

在上一章数组章节中,你看到了带有 @ 下标的 "${myarray[@]}",它用于把数组展开成各个独立的元素。 但有时你想把所有元素连接成一个字符串。 这时可以使用 * 下标:

echo "${myarray[*]}"

展开式必须用双引号括起来。

Bash 使用内置变量 IFS 的第一个字符作为分隔字符。 默认情况下,$IFS 由空格、制表符和换行符组成。

myarray=(one two three four)
mystring="${myarray[*]}"
declare -p mystring
# => declare -- mystring="one two three four"

我们可以修改 IFS 变量,改用不同的分隔字符:

myarray=(one two three four)
IFS=","
mystring="${myarray[*]}"
declare -p mystring
# => declare -- mystring="one,two,three,four"
Advanced
我们可以把它封装成一个函数。 点击查看详情。
join() {
    local IFS=$1
    shift
    local elements=("$@")
    echo "${elements[*]}"
}

join ":" "${myarray[@]}"     # note, the "@" index
# => "one:two:three:four"

在函数内把 IFS 局部化,意味着我们不必保存旧值,再把它恢复成全局作用域中的原值。

更精妙的是,特殊参数 "$*" 加上引号后就具有相同的功能,因此我们不需要保存一份函数实参的副本:

join() {
    local IFS=$1
    shift
    echo "$*"
}
如果不使用函数,在子 shell 中修改 IFS 是避免改动当前 shell 中 IFS 的好办法。 点击查看详情。
(IFS=","; echo "${myarray[*]}")

括号会创建一个子 shell(当前 shell 的副本)。括号内的命令执行完毕后,子 shell 退出,被修改的 IFS 变量也随之消失。

注意,这样写不起作用:IFS="," echo "${myarray[*]}",参数展开会先执行,之后 shell 才会把修改后的 IFS 变量应用到 echo 命令上。

数组切片

你可能见过 "${variable:offset:length}" 这种参数展开,它会展开成变量值中的子字符串。 对数组也可以这样做,从而展开数组的一个切片。

myarray=(one two three four)

subarray=("${myarray[@]:0:2}")
declare -p subarray
# => declare -a subarray=([0]="one" [1]="two")

subarray=("${myarray[@]:1:3}")
declare -p subarray
# => declare -a subarray=([0]="two" [1]="three" [2]="four")

省略长度部分表示“从偏移量到数组末尾”:

subarray=("${myarray[@]:2}")
declare -p subarray
# => declare -a subarray=([0]="three" [1]="four")

把数组传给函数

这并不像你可能熟悉的其他语言那样直接。 把数组传给函数主要有两种方法。

传递元素

第一种方法是把数组的所有值都传进去,在函数中把它们收集到一个局部数组里。

myfunc() {
    local array_copy=("$@")
    # do stuff with array_copy
    declare -p array_copy
}

array_original=(11 22 33 44)
myfunc "${array_original[@]}"

函数中的数组保存的是这些值的副本。 在函数里对数组所做的任何修改都不会反映到外层作用域。

传递数组名

这种方法更像你可能从其他语言中了解的“按引用传递”能力。 你以字符串的形式传入数组的名字。 函数会创建一个带有 “nameref” 属性的局部变量。 这个局部数组和全局数组(也就是我们传入名字的那个)正是同一个数组。

myfunc() {
    # note the `-n` option
    local -n local_array=$1

    # do stuff with local_array
    for i in "${!local_array[@]}"; do
        printf '%d => %s\n' "$i" "${local_array[i]}"
    end

    # we can mutate it
    local_array+=(55 66 77)
}

array_original=(11 22 33 44)
myfunc "array_original"

# show the mutated array
declare -p array_original
# => declare -a array_original=([0]="11" [1]="22" [2]="33" [3]="44" [4]="55" [5]="66" [6]="77")

nameref 也适用于关联数组和“标量”变量(也就是保存字符串值的变量)。

Note

在函数内部,declare -p local_array 帮不上太多忙。 它只会输出 declare -n local_array="array_original"。 你可以通过查看传入的数组名来获取该数组的详细信息:declare -p "$1"

Caution

注意让局部数组的名字与传入的数组不同。 代码仍然可以运行,但会发出这样的 “circular name reference” 警告:

myfunc() {
    local -n a=$1
    local IFS=,
    echo "${a[*]}"
}

# same name as the function's local variable
a=(one two three)
myfunc a
bash: local: warning: a: circular name reference
bash: warning: a: circular name reference
bash: warning: a: circular name reference
bash: warning: a: circular name reference
one,two,three

位置参数“类似数组”

在只力求符合 POSIX 标准的 shell(如 ash 和 dash)中,并没有数组。 最接近的办法就是使用位置参数。

  • 位置参数通过下标访问:$1、$2 等。
  • 用 "$@" 可以把它们展开成各个独立的元素
  • 用 "$*" 可以把它们拼接成一个字符串
  • 参数的个数是 $#

使用 set 命令给它们赋值:

set -- one two three
set -- "$@" four

for item in "$@"; do
    echo "do something with $item"
done

如果你的目标是编写“可移植”的 shell 脚本,你会用位置参数来保存一组值的“列表”。

通过 GitHub 编辑 该链接会在新窗口或标签页中打开