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'
但对于本例来说,无需担心。
第二个例子似乎更容易写,我错过了什么吗?
即使在之后,它们也根本不是同一件事
IFS=$'\n'
。特别是在 bash 中(尽管该语法是从 zsh 借用的):
(
arr+=( $(cmd) )
将用于将元素追加到数组;因此将与keys=( -1 "${!arr[@]}" ); readarray -tO "$(( ${keys[@]: -1} + 1))" arr < <(cmd)
² 进行比较)。做:
cmd
,其标准输出在管道的写入端打开。$IFS
特殊变量的内容分割结果字符串。对于其中的$IFS
空白字符(例如换行符),行为更加复杂:printf '\n\n\na\n\n\nb\n\n\n'
仅分为两个元素:a
和b
。noglob
,nullglob
,failglob
,extglob
,globasciiranges
,glabstar
,nocaseglob
。这适用于那些包含*
、?
、[
、 和 某些 bash 版本的单词,如果启用,\
则更多。extglob
$arr
数组。例子:
正如您所看到的,该
$'foo\n\n\n\n*'
文件被拆分为foo
和*
,并*
扩展为当前工作目录中的文件列表,这解释了为什么我们同时得到foo
和$'foo\n\n\n\n*'
,同样?x
解释了为什么我们得到\x
(显示为"\\x"
) 3 次,因为有\x
行的输出与和ls
都匹配。*
?x
使用 bash 5.0,我们得到:
在该版本中,反斜杠只有
\x
两次但x
三倍,即使后面没有跟随一个通配符,反斜杠也是一个通配符,因此\x
作为通配符匹配x
。之后
shopt nocaseglob
,我们得到:aX
显示 3 次,因为它也?x
匹配。之后
shopt -s failglob
:和
arr=( $(echo '/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*') )
使系统无法使用几分钟后内存不足。
因此,总而言之,
IFS=$'\n'; arr=( $(cmd) )
不会将 的输出行存储cmd
在数组中,而是将其输出的非空行扩展所产生的文件名cmd
视为 glob 模式。使用
mapfile
或较少误导性的readarray
别名:cmd
在子 shell 中运行,其标准输出在管道的写入端打开。<(...)
被扩展为类似/dev/fd/63
或/proc/self/fd/63
where63
is a file descriptor of theparent shell open on the read end of the pipeline.<
重定向缩写0<
,打开 /dev/fd/63 以在 fd 0 上读取,这意味着 的 stdinreadarray
也将是该管道的读取端。readarray
从该管道读取每一行(同时cmd
写入它),丢弃行分隔符 (-t
),并将其存储在$arr
.所以最后
$arr
,假设cmd
输出没有 NUL 将包含输出的每一行的内容cmd
,无论它们是否为空或是否包含全局字符。以上面的例子:
这与我们在之前的输出中看到的一致
ls | cat
,但如果目的是获取当前工作目录中的文件列表,那么这仍然是错误的。的输出ls
无法进行后处理,除非您使用 GNU 实现的某些扩展,ls
例如--quoting-style=shell-always
或--zero
最新版本(9.0 或更高版本):这次,将 NUL限制记录
readarray
的内容存储到 中。不能用于,因为不能在其变量中存储 NUL。d
$arr
IFS=$'\0'
bash
bash
或者:
无论如何,将当前工作目录中的文件列表放入数组的正确方法是:
仅
ls --zero
当您希望列表按大小或修改时间排序时,您才会使用 bash glob(与 zsh 相反)无法做到的事情。如:
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
的存在状态。cmd
wait "$!"; cmd_status=$?
¹
arr=( ... )
语法来自 zsh(bash 直到 1996 年 2.0 才出现数组),但请注意,在 zsh 中,命令替换虽然也会删除尾随换行符并受 -stripping 约束,但不会丢弃 NUL(NUL 甚至$IFS
在那里的默认值$IFS
)并且不像其他类似 Bourne 的 shell 那样受到通配符的影响,这有助于使其成为一个更安全的 shell。²
readarray
又名mapfile
没有追加模式,但在最近的版本中,您可以告诉它第一个元素的索引,从哪里开始存储元素,-O
如下所示。要找出 bash 中最后一个元素的索引(其中数组像 ksh 中一样稀疏!),这是非常困难的。cmd
在这里附加to的输出行$arr
,而不是那些非常复杂的代码,您不妨将这些行读入临时数组 withreadarray -r tmp < <(cmd)
并将元素附加到$arr
witharr+=( "${tmp[@]}" )
。另请注意,如果arr
变量被声明为标量或关联,则这些方法之间的行为会有所不同。