适用于 Linux 用户的 2020 年更新:
如果您有最新版本的 bash(4.4-alpha 或更高版本),就像您在 Linux 上可能那样,那么您应该使用Benjamin W.'s answer。
如果您使用的是 Mac OS(我上次检查过)仍然使用 bash 3.2,或者使用的是较旧的 bash,请继续阅读下一节。
回答 bash 4.3 或更早版本
这是将find 的输出放入bash 数组的一种解决方案:
array=()
while IFS= read -r -d $'\0'; do
array+=("$REPLY")
done < <(find . -name "${input}" -print0)
这很棘手,因为通常文件名可以包含空格、换行符和其他脚本敌对字符。使用find 并使文件名彼此安全分隔的唯一方法是使用-print0,它会打印以空字符分隔的文件名。如果 bash 的 readarray/mapfile 函数支持空分隔字符串但它们不支持,这不会带来太大的不便。 Bash 的 read 确实如此,这将我们引向上面的循环。
[此答案最初写于 2014 年。如果您有最新版本的 bash,请查看下面的更新。]
工作原理
第一行创建一个空数组:array=()
每次执行read 语句时,都会从标准输入中读取一个以空值分隔的文件名。 -r 选项告诉read 单独留下反斜杠字符。 -d $'\0' 告诉 read 输入将以空值分隔。由于我们省略了read 的名称,因此shell 将输入放入默认名称:REPLY。
array+=("$REPLY") 语句将新文件名附加到数组array。
最后一行结合了重定向和命令替换,将find 的输出提供给while 循环的标准输入。
为什么要使用进程替换?
如果我们不使用进程替换,循环可以写成:
array=()
find . -name "${input}" -print0 >tmpfile
while IFS= read -r -d $'\0'; do
array+=("$REPLY")
done <tmpfile
rm -f tmpfile
在上面find 的输出存储在一个临时文件中,该文件用作while 循环的标准输入。进程替换的想法是使此类临时文件变得不必要。所以,不是让while 循环从tmpfile 获取它的标准输入,我们可以让它从<(find . -name ${input} -print0) 获取它的标准输入。
进程替换非常有用。在命令想要从文件读取的许多地方,您可以指定进程替换<(...),而不是文件名。有一个类似的形式,>(...),可以用来代替命令要写入到文件的文件名。
与数组一样,进程替换是 bash 和其他高级 shell 的一项功能。它不是 POSIX 标准的一部分。
替代方案:lastpipe
如果需要,可以使用lastpipe 代替进程替换(帽子提示:Caesar):
set +m
shopt -s lastpipe
array=()
find . -name "${input}" -print0 | while IFS= read -r -d $'\0'; do array+=("$REPLY"); done; declare -p array
shopt -s lastpipe 告诉 bash 在当前 shell(而不是后台)中运行管道中的最后一个命令。这样,array 在管道完成后仍然存在。因为lastpipe只有在job control关闭的情况下才会生效,所以我们运行set +m。 (在脚本中,与命令行相反,作业控制默认关闭。)
补充说明
以下命令创建一个 shell 变量,而不是一个 shell 数组:
array=`find . -name "${input}"`
如果你想创建一个数组,你需要在 find 的输出周围加上括号。所以,天真地,一个人可以:
array=(`find . -name "${input}"`) # don't do this
问题是shell对find的结果进行了分词,所以不能保证数组的元素就是你想要的。
2019 年更新
从 4.4-alpha 版本开始,bash 现在支持 -d 选项,因此不再需要上述循环。相反,可以使用:
mapfile -d $'\0' array < <(find . -name "${input}" -print0)
有关这方面的更多信息,请参阅(并投票)Benjamin W.'s answer。