如何使用数组变量(二)?

超级欧派课程 2024-03-09 00:18:12

接上篇《如何使用数组变量(一)?》2.1.2. 其他方法

有时候删除空行是可取的,或者您可能知道输入始终以换行符分隔,例如由脚本内部生成的输入。在某些Shell中,可以使用-d标志将read的行分隔符设置为null,然后滥用通常用于将行的字段读入数组的-a或-A标志,用于读取行。实际上,整个输入被视为单行,字段以换行符分隔。

# Bash 4IFS=$'\n' read -rd '' -a lines <file# mksh, zshIFS=$'\n' read -rd '' -A lines <file2.1.3. 不要使用for循环读取行!

依赖于IFS WordSplitting会导致问题,如果有重复的空白分隔符,它们将被合并。通过这种方式无法将空行保存为空数组元素。更糟糕的是,特殊的通配符字符将被展开,而不需要禁用然后重新启用它。永远不要使用这种方法-它是有问题的,解决方法都很丑陋,并且并非所有问题都能解决。

2.2. 读取以NUL字符分隔的流

如果您尝试处理可能包含嵌入换行符的记录,您将使用替代分隔符(如NUL字符\0)来分隔记录。在bash 4.4中,您可以简单地使用mapfile -t -d ''命令:

# Bash 4.4mapfile -t -d '' files < <(find . -name '*.ugly' -print0)

否则,您需要在循环内使用-d参数来进行读取:

# Bashwhile read -rd ''; do arr[i++]=$REPLYdone < <(find . -name '*.ugly' -print0)# 或者(bash 3.1及更高版本)while read -rd ''; do arr+=("$REPLY")done < <(find . -name '*.ugly' -print0)

read -d ''告诉Bash在读取到NUL字节而不是换行符之前继续读取。这不能保证在所有具有-d特性的shell中都有效。

如果您选择给read命令指定一个变量名而不是使用REPLY,则还要确保为read命令设置IFS=,以避免修剪前导/尾随的IFS空格。

2.3. 追加到现有数组

如前所述,数组是稀疏的 - 也就是说,不保证相邻的数字索引被值占用。这使得“追加”到现有数组的含义变得混乱。有几种方法可以解决这个问题。

如果您一直使用一个变量来跟踪最高编号的索引(例如,在循环中填充数组的副作用),并且可以保证它是正确的,那么您可以继续使用它并确保它保持同步。

# Bash/ksh93arr[++i]="new item"

如果您不想保留索引变量,但碰巧知道您的数组不是稀疏的,那么您可以使用元素数量来计算偏移量(不推荐):

# Bash/ksh# 如果数组有空洞(稀疏),则此方法将失败。arr[${#arr[@]}]="new item"

如果您不知道数组是否稀疏,但不介意重新索引整个数组(非常低效),那么可以使用以下方法:

# Basharr=("${arr[@]}" "new item")# Kshset -A arr -- "${arr[@]}" "new item"

如果您使用的是bash 3.1或更高版本,则可以使用+=运算符:

# Bash 3.1, ksh93, mksh, zsharr+=(item 'another item')

注意:圆括号是必需的,就像赋值给数组时一样。否则,您将追加到arr是其同义词。如果您的shell支持此类型的追加,这是首选的方法。

有关使用数组保存复杂shell命令的示例,请参见我之前的文章。

3. 从数组中检索值

${#arr[@]}​ 或 ${#arr[*]}​ 展开为数组中元素的数量:

# Bashshopt -s nullgloboggs=(*.ogg)echo "有 ${#oggs[@]} 个Ogg文件。"

可以通过索引来检索单个元素:

echo "${foo[0]} - ${bar[j+1]}"

方括号是一个数学上下文。在数学上下文中,变量(包括数组)可以通过名称引用。例如,在扩展中:

​${arr[x[3+arr[2]]]}​

arr的索引将是数组x中索引为3的值加上arr[2]的值。

对数组元素进行批量操作是shell数组的一个重要特性。与位置参数展开"$@"​完全相同,${arr[@]}​被展开为一个词的列表,每个数组元素占据一个词。例如:

# Korn/Bashfor x in "${arr[@]}"; do echo "下一个元素是 '$x'"done

即使元素包含空格,这也能正常工作。你总是得到与数组元素数量相同的词数。

如果只想将完整的数组以每行一个元素的形式输出,这是最简单的方法:

# Bash/kshprintf "%s\n" "${arr[@]}"

对于稍微复杂一些的数组输出,${arr[*]}​ 会将元素连接在一起,第一个字符为 *​ 的展开方式与位置参数相同。

# Basharr=(x y z)IFS=/; echo "${arr[*]}"; unset -v IFS# 输出 x/y/z

不幸的是,你不能使用该语法在数组元素之间放置多个字符。相反,你需要采用以下方法:

# Bash/ksharr=(x y z)tmp=$(printf "%s<=>" "${arr[@]}")echo "${tmp%<=>}" # 移除末尾的额外 <=> 符号# 输出 x<=>y<=>z

或者使用数组切片,参见下一节。

# Bash/kshtypeset -a a=([0]=x [5]=y [10]=z)printf '%s<=>' "${a[@]::${#a[@]}-1}"printf '%s\n' "${a[@]:(-1)}"

这个例子还展示了如何一次给稀疏数组分配多个元素。请注意,使用 arr=([key]=value ...)​ 的语法在不同的shell中有所不同。在ksh93中,默认情况下,此语法会给你一个关联数组,除非你另行指定,并且使用它需要为每个值显式给出一个索引,不像bash中,省略的索引从前一个索引开始。这个例子以一种在两种shell中都兼容的方式编写。

BASH 3.0 添加了检索数组中索引值列表的功能:

# Bash 3.0 或更高版本arr=(0 1 2 3) arr[42]='问题是什么?'unset -v 'arr[2]'echo "${!arr[@]}"# 输出 0 1 3 42

检索索引对于某些任务非常重要,例如维护具有相同索引的并行数组(在没有结构体的语言中模拟数组)。例如:

# Bash 3.0 或更高版本unset -v file title artist ifor f in ./*.mp3; do file[i]=$f title[i]=$(mp3info -p %t "$f") artist[i++]=$(mp3info -p %a "$f")done# 后来,遍历每首歌曲。# 这适用于数组是稀疏的情况,只要它们都有相同的空洞。for i in "${!file[@]}"; do echo "${file[i]} 是 ${title[i]} 的 ${artist[i]}"done3.1. 带修改的检索

Bash的参数展开可以对数组元素进行批量操作:

# Basharr=(abc def ghi jkl)echo "${arr[@]#?}" # 输出 bc ef hi klecho "${arr[@]/[aeiou]/}" # 输出 bc df gh jkl

参数展开还可以用于从数组中提取子列表,有些人称之为切片操作:

# Bashecho "${arr[@]:1:3}" # 输出从#1(第二个元素)开始的三个元素echo "${arr[@]:(-2)}" # 输出最后两个元素

对于位置参数也是一样的操作

切换行号显示

set -- foo bar bazecho "${@:(-1)}" # 输出最后一个位置参数 bazecho "${@:(-2):1}" # 输出倒数第二个位置参数 bar4. 将@作为伪数组使用

正如我们在上面看到的,@数组(位置参数数组)几乎可以像常规命名数组一样使用。这是POSIX或Bourne shell中唯一可用的数组。它有一定的限制:你不能单独设置或取消单个元素,并且它不能是稀疏的。尽管如此,它仍然使得一些在POSIX shell下需要使用外部工具的任务成为可能:

# POSIXset -- *.mp3if [ -e "$1" ] || [ -L "$1" ]; then echo "there are $# MP3 files"else echo "there are 0 MP3 files"fi# POSIX...# 向我们动态生成的选项列表添加一个选项set -- "$@" -f "$somefile"...foocommand "$@"

(与之前文章中使用命名数组动态生成命令进行比较)

如果您觉得文章内容对你有一点帮助可以关注我,我在头条平台会持续分享更多实用的shell技巧和最佳实践,如果想系统的快速学习shell的各种高阶用法和生产环境避坑指南可以看看《shell脚本编程最佳实践》专栏,专栏里有更多的实用小技巧和脚本代码分享。

0 阅读:0

超级欧派课程

简介:感谢大家的关注