创建并附加到数组,mapfile 与 arr+=(input) 相同,还是我遗漏了什么?

创建并附加到数组,mapfile 与 arr+=(input) 相同,还是我遗漏了什么?

mapfile没有好处超过的情况arr+=(input)

简单的例子

映射文件数组名称,arr:

mkdir {1,2,3}

mapfile -t arr < <(ls)

declare -p arr

输出:

declare -a arr=([0])="1" [1]="2" [2]="3")

编辑:

更改了以下标题;正文具有y数组名称,但标题具有arr名称,这可能会导致混乱。

y+=(输入)

IFS=$'\n'

y+=($(ls))

declare -p y

输出:

declare -a y=([0])="1" [1]="2" [2]="3")

我认为一个优点mapfile是你不必担心分词。

对于另一种方式,您可以通过设置来避免分词IFS=$'\n',但对于本例来说,无需担心。

第二个例子似乎更容易写,我错过了什么吗?

答案1

即使在之后,它们也根本不是同一件事IFS=$'\n'

特别是在 bash 中(尽管该语法是从 zsh 借用的):

arr=( $(cmd) )

arr+=( $(cmd) )将用于附加数组中的元素;因此将与keys=( -1 "${!arr[@]}" ); readarray -tO "$(( ${keys[@]: -1} + 1))" arr < <(cmd)²)进行比较。

做:

  1. 在子 shell 中运行cmd,其标准输出在管道的写入端打开。
  2. 同时,父 shell 进程从管道的另一端读取数据并:
    • 删除 NUL 字符和尾随换行符
    • 根据$IFS特殊变量的内容分割结果字符串。对于其中的$IFS空白字符(例如换行符),行为更加复杂:
      • 前导和尾随的被删除(在换行符的情况下,它们已被命令替换删除,如上所示)
      • 一个或多个序列被视为分隔器。例如, 的输出printf '\n\n\na\n\n\nb\n\n\n'仅分为两个元素:ab
    • 然后,这些单词中的每一个都将受到文件名生成(又名通配符)的影响,其行为受到许多选项的影响,包括noglob, nullglob, failglob, extglob, globasciiranges, glabstar, nocaseglob。这适用于那些包含*?[、 和 某些 bash 版本的单词,如果启用,\则更多。extglob
  3. 然后将得到的单词作为元素分配给$arr数组。

例子:

bash-5.1$ touch x '\x' '?x' aX $'foo\n\n\n\n*'
bash-5.1$ IFS=$'\n'
bash-5.1$ ls | cat
aX
foo



*
?x
\x
x
bash-5.1$ arr=( $(ls) )
bash-5.1$ typeset -p arr
declare -a arr=([0]="aX" [1]="foo" [2]="aX" [3]=$'foo\n\n\n\n*' [4]="?x" [5]="\\x" [6]="x" [7]="?x" [8]="\\x" [9]="\\x" [10]="x")

正如您所看到的,该$'foo\n\n\n\n*'文件被拆分为foo和 ,**扩展为当前工作目录中的文件列表,这解释了为什么我们同时得到foo$'foo\n\n\n\n*',同样?x解释了为什么我们得到\x(显示为"\\x") 3 次,因为有\x行的输出与和ls都匹配。*?x

使用 bash 5.0,我们得到:

bash-5.0$ arr=( $(ls) )
bash-5.0$ typeset -p arr
declare -a arr=([0]="aX" [1]="foo" [2]="aX" [3]=$'foo\n\n\n\n*' [4]="?x" [5]="\\x" [6]="x" [7]="?x" [8]="\\x" [9]="x" [10]="x")

在该版本中,反斜杠只有两次但三倍,即使后面没有跟随一个通配符,反斜杠也是一个通配符,因此作为\x通配符匹配。x\xx

之后shopt nocaseglob,我们得到:

bash-5.1$ shopt -s nocaseglob
bash-5.1$ arr=( $(ls) )
bash-5.1$ typeset -p arr
declare -a arr=([0]="aX" [1]="foo" [2]="aX" [3]=$'foo\n\n\n\n*' [4]="?x" [5]="\\x" [6]="x" [7]="aX" [8]="?x" [9]="\\x" [10]="\\x" [11]="x")

显示aX3 次,因为它?x也匹配。

shopt -s failglob

bash-5.0$ shopt -s failglob
bash-5.0$ arr=( $(printf '\\z\n') )
bash: no match: \z
bash-5.0$ arr=( $(printf 'WTF\n?') )
bash: no match: WTF?

arr=( $(echo '/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*') )

使系统无法使用几分钟后内存不足。

因此,总而言之,IFS=$'\n'; arr=( $(cmd) )不会将 的输出行存储cmd在数组中,而是将其输出的非空行扩展所产生的文件名cmd视为 glob 模式。


使用mapfile或较少误导性的readarray别名:

readarray -t arr < <(cmd)
  1. 如上所述,cmd在子 shell 中运行,其标准输出在管道的写入端打开。
  2. the<(...)被扩展为类似/dev/fd/63/proc/self/fd/63where 63is a file descriptor of theparent shell open on the read end of the pipeline.
  3. 使用<的重定向缩写0<,打开 /dev/fd/63 以在 fd 0 上读取,这意味着 的 stdinreadarray也将是该管道的读取端。
  4. readarray从该管道读取每一行(同时cmd写入它),丢弃行分隔符 ( -t),并将其存储在$arr.

所以最后$arr,假设cmd输出没有 NUL 将包含输出的每一行的内容cmd,无论它们是否为空或是否包含全局字符。

以上面的例子:

bash-5.1$ readarray -t arr < <(ls)
bash-5.1$ typeset -p arr
declare -a arr=([0]="aX" [1]="foo" [2]="" [3]="" [4]="" [5]="*" [6]="?x" [7]="\\x" [8]="x")

这与我们在之前的输出中看到的一致ls | cat,但如果目的是获取当前工作目录中的文件列表,那么这仍然是错误的。的输出ls无法进行后处理,除非您使用 GNU 实现的某些扩展,ls例如--quoting-style=shell-always--zero最新版本(9.0 或更高版本):

bash-5.2$ readarray -td '' arr < <(ls --zero)
bash-5.2$ typeset -p arr
declare -a arr=([0]="aX" [1]=$'foo\n\n\n\n*' [2]="?x" [3]="\\x" [4]="x")

这次,将 NUL限制记录readarray的内容存储到 中。不能用于,因为不能在其变量中存储 NUL。d$arrIFS=$'\0'bashbash

或者:

bash-5.1$ eval "arr=( $(ls --quoting-style=shell-always) )"
bash-5.1$ typeset -p arr
declare -a arr=([0]="aX" [1]=$'foo\n\n\n\n*' [2]="?x" [3]="\\x" [4]="x")

无论如何,将当前工作目录中的文件列表放入数组的正确方法是:

shopt -s nullglob
arr=( * )

ls --zero当您希望列表按大小或修改时间排序时,您才会使用 bash glob(与 zsh 相反)无法做到的事情。

如:

桀骜 最近的 GNU bash + GNU coreutils
new_to_old=( *.txt(Nom) ) readarray -td '' new_to_old < <(ls -td --zero -- *.txt)
four_largest=( *.txt(NOL[1,4]) ) readarray -td '' four_largest < <(ls -tdrS --zero -- *.txt | head -zn4)

a=($(cmd))和之间的另一个区别readarray < <(cmd)是退出状态,前者是cmd,后者是readarray。使用最新版本的,您可以使用 获取后者bash的存在状态。cmdwait "$!"; cmd_status=$?


¹arr=( ... )语法来自 zsh(bash 直到 1996 年 2.0 才出现数组),但请注意,在 zsh 中,命令替换虽然也会删除尾随换行符并受$IFS-stripping 约束,但不会丢弃 NUL(NUL 甚至在那里的默认值$IFS)并且不像其他类似 Bourne 的 shell 那样受到通配符的影响,这有助于使其成为一个更安全的 shell。

²readarray又名mapfile没有追加模式,但在最近的版本中,您可以告诉它第一个元素的索引,从哪里开始存储元素,如下-O所示。要找出 bash 中最后一个元素的索引(其中数组像 ksh 中一样稀疏!),这是非常困难的。在这里附加cmdto的输出行$arr,而不是那些非常复杂的代码,您不妨将这些行读入临时数组 withreadarray -r tmp < <(cmd)并将元素附加到$arrwith arr+=( "${tmp[@]}" )。另请注意,如果arr变量被声明为标量或关联,则这些方法之间的行为会有所不同。

相关内容