如何将查找命令结果作为数组存储在Bash中

Jun*_* Oh 62 arrays variables bash find

我试图将查找结果保存为数组.这是我的代码:

#!/bin/bash

echo "input : "
read input

echo "searching file with this pattern '${input}' under present directory"
array=`find . -name ${input}`

len=${#array[*]}
echo "found : ${len}"

i=0

while [ $i -lt $len ]
do
echo ${array[$i]}
let i++
done
Run Code Online (Sandbox Code Playgroud)

我在当前目录下获得了2个.txt文件.所以我期待'2'作为结果find.然而,它打印1.原因是它将查找的所有结果作为一个元素.我怎样才能解决这个问题?谢谢.

PS我在Stack OverFlow中找到了几个关于类似问题的解决方案.但是,它有点不同所以我可以申请我的.我需要在循环之前将结果存储到变量.再次感谢.

Joh*_*024 97

下面是用于获取的输出的一个溶液find到一个bash数组:

array=()
while IFS=  read -r -d $'\0'; do
    array+=("$REPLY")
done < <(find . -name "${input}" -print0)
Run Code Online (Sandbox Code Playgroud)

这很棘手,因为通常,文件名可以包含空格,换行符和其他脚本恶意字符.使用find和使文件名彼此安全分离的唯一方法是使用-print0打印用空字符分隔的文件名.如果bash的readarray/ mapfile函数支持以空值分隔的字符串但它们不支持,那么这不会带来太多麻烦.Bash read的确如此,这导致我们进入上面的循环.

这个怎么运作

  1. 第一行创建一个空数组: array=()

  2. 每次read执行该语句时,都会从标准输入中读取以空值分隔的文件名.该-r选项告诉read单独留下反斜杠字符.该-d $'\0'通知read的投入将是空分隔.由于我们省略了名称read,因此shell将输入放入默认名称:REPLY.

  3. array+=("$REPLY")语句将新文件名附加到数组中array.

  4. 最后一行结合了重定向和命令替换,以提供循环find标准输入的输出while.

为何使用流程替代?

如果我们不使用进程替换,则循环可以写为:

array=()
find . -name "${input}" -print0 >tmpfile
while IFS=  read -r -d $'\0'; do
    array+=("$REPLY")
done <tmpfile
rm -f tmpfile
Run Code Online (Sandbox Code Playgroud)

在上面,输出find存储在临时文件中,该文件用作while循环的标准输入.进程替换的想法是不需要这样的临时文件.所以,不是让while循环得到它的标准输入tmpfile,我们可以让它得到它的标准输入<(find . -name ${input} -print0).

过程替换非常有用.在命令要从文件中读取的许多位置,您可以指定进程替换<(...),而不是文件名.有一种类似的形式,>(...)可用于代替命令要写入文件的文件名.

与数组一样,进程替换是bash和其他高级shell的一个特性.它不是POSIX标准的一部分.

补充说明

以下命令创建shell变量,而不是shell数组:

array=`find . -name "${input}"`
Run Code Online (Sandbox Code Playgroud)

如果要创建数组,则需要在find的输出周围放置parens.所以天真地,人们可以:

array=(`find . -name "${input}"`)  # don't do this
Run Code Online (Sandbox Code Playgroud)

问题是shell对结果执行了单词拆分,find因此不保证数组的元素是你想要的.

  • @Rockallite这是一个很好的观察,但是不完整。虽然确实没有将我们分成多个单词,但是我们仍然需要`IFS =`以避免从输入行的开头或结尾删除空格。您可以通过比较`read var &lt;&lt;&lt;'abc';的输出来轻松测试。回显“&gt; $ var &lt;”,输出为IFS = read var &lt;&lt;&lt;'abc'; echo“&gt; $ var &lt;”`。在前一种情况下,删除了“ abc”之前和之后的空格。在后者中则不是。以空格开头或结尾的文件名可能很特殊,但是,如果存在,我们希望它们正确处理。 (2认同)

che*_*ner 15

如果您使用bash4或更高版本,您可以取代你使用的find

shopt -s globstar nullglob
array=( **/*"$input"* )
Run Code Online (Sandbox Code Playgroud)

匹配0或更多目录**启用的模式globstar,允许模式匹配当前目录中的任意深度.如果没有该nullglob选项,模式(在参数扩展之后)将按字面处理,因此如果没有匹配,您将拥有一个包含单个字符串而不是空数组的数组.

dotglob如果您想要遍历隐藏目录(例如.ssh)并匹配隐藏文件(例如.bashrc),也可以将选项添加到第一行.

  • 请注意,这将不包括隐藏文件和目录,除非`dotglob`设置(这可能会或可能不会被通缉,但它是值得一提过). (4认同)
  • 也许`nullglob`也是...... (3认同)

Ben*_* W. 14

Bash 4.4 -dreadarray/ 引入了一个选项mapfile,因此现在可以通过以下方式解决

readarray -d '' array < <(find . -name "$input" -print0)
Run Code Online (Sandbox Code Playgroud)

一种适用于任意文件名(包括空格,换行符和通配符)的方法。

手册中(省略其他选项):

mapfile [-d delim] [array]
Run Code Online (Sandbox Code Playgroud)

-d
的第一个字符delim用于终止每个输入行,而不是换行符。如果delim为空字符串,mapfile则在读取NUL字符时将终止一行。

并且readarray只是的同义词mapfile

  • @dpritch受[这个答案](https://unix.stackexchange.com/a/176703/118235)的启发,您可以打印退出状态作为进程替换的一部分: `readarray -d '' array &lt; &lt;(find .-name "$input" -print0; printf "$?")`,然后检查最后一个数组元素:`echo "${array[-1]}"`。 (2认同)

sun*_*sen 12

以下似乎适用于 macOS 上的 Bash 和 Z Shell。

#! /bin/sh

IFS=$'\n'
paths=($(find . -name "foo"))
unset IFS

printf "%s\n" "${paths[@]}"
Run Code Online (Sandbox Code Playgroud)

  • 也许我在这里遗漏了一些东西,但这对于 99% 的情况来说似乎是更清晰、更简单的解决方案 (5认同)
  • 这适用于具有空格和其他特殊字符的文件,但在文件名中有换行符的情况下会失败(诚然罕见)。您可以使用 `printf "%b" "文件名包含空格、星号 * ...\012 和第二行 \0" | 创建一个测试。xargs -0 触摸` (2认同)

Ahm*_*far 6

你可以试试像

array=(`find . -type f | sort -r | head -2`)
,为了打印数组值,你可以尝试像echo这样的东西 "${array[*]}"

  • 如果文件名包含空格或glob字符,则会中断. (5认同)