如何在 Bash 中将“ find”命令结果存储为数组

我试图将来自 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

我得到了2. txt 文件的工作目录。 所以我期望’2’作为 ${len}的结果。但是,它打印1。 原因是它将 find的所有结果作为一个元素。 我该怎么补救?

附言
我在 StackOverFlow 上找到了几个解决类似问题的方案。然而,他们有一点点不同,所以我不能申请我的情况。我需要在循环之前将结果存储在一个变量中。再次感谢。

134794 次浏览

Linux 用户更新2020:

如果您有一个最新版本的 bash (4.4-alpha 或更高版本) ,就像您在 Linux 上所做的那样,那么您应该使用 本杰明 · W 的回答

如果您使用的是 Mac OS (我上次检查的时候,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函数支持空分隔的字符串,那么这不会造成很大的不便,但是它们不支持。巴斯的 read就是这样,这就把我们引向了上面的循环。

[这个答案最初写于2014年。如果您有 bash 的最新版本,请参阅下面的更新。]

它是如何工作的

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

  2. 每次执行 read语句时,从标准输入读取一个空分隔的文件名。-r选项告诉 read不要使用反斜杠字符。-d $'\0'告诉 read输入将以空分隔。因为我们省略了 read的名称,所以 shell 将输入放入默认名称: REPLY

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

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

为什么使用过程替换?

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

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

在上面的代码中,find的输出存储在一个临时文件中,该文件用作 while 循环的标准输入。进程替换的想法是使这样的临时文件变得不必要。因此,我们不需要让 while循环从 tmpfile获取它的 stdin,而是让它从 <(find . -name ${input} -print0)获取它的 stdin。

进程替换非常有用。在许多地方,如果命令希望从文件中取得 ,则可以指定进程替换 <(...),而不是指定文件名。有一个类似的表单 >(...),可以用来替代命令希望 写作指向文件的文件名。

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

另一个选择: 最后一根管子

如果需要,可以使用 lastpipe代替进程替换(帽子提示: 凯撒) :

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只在作业控制关闭时生效,所以我们运行 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)

有关这方面的更多信息,请参见(和更新) 本杰明 · W 的回答

你可以这样做:

#!/bin/bash
echo "input : "
read input


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


for i in "${array[@]}"
do :
echo $i
done

如果您使用的是 bash4或更高版本,则可以使用

shopt -s globstar nullglob
array=( **/*"$input"* )

globstar启用的 **模式匹配0个或多个目录,允许模式匹配任意深度的工作目录。如果没有 nullglob选项,模式(在参数扩展之后)将按字面意义处理,因此如果没有匹配,那么您将只有一个带有单个字符串的数组,而不是一个空数组。

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

你可以试试

array=(`find . -type f | sort -r | head -2`)
, and in order to print the array values , you can try something like echo "${array[*]}"

在 bash 中,$(<any_shell_cmd>)帮助运行命令并捕获输出。将其作为分隔符传递给 IFS有助于将其转换为数组。

IFS='\n' read -r -a txt_files <<< $(find /path/to/dir -name "*.txt")

Bash 4.4为 readarray/mapfile引入了一个 -d选项,因此现在可以用

readarray -d '' array < <(find . -name "$input" -print0)

用于处理任意文件名(包括空格、换行符和全局字符)的方法。这要求您的 find支持 -print0,例如 GNUfind 就支持 -print0

手动操作(省略其他选项) :

mapfile [-d delim] [array]

-d
The first character of delim is used to terminate each input line, rather than newline. If delim is the empty string, mapfile will terminate a line when it reads a NUL character.

And readarray is just a synonym of mapfile.

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

#! /bin/sh


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


printf "%s\n" "${paths[@]}"

这些解决方案都不适合我,因为我不喜欢学习 readarray 和 mapfile。这是我想到的。

#!/bin/bash


echo "input : "
read input


echo "searching file with this pattern '${input}' under present directory"
# The only change is here. Append to array for each non-empty line.
array=()
while read line; do
[[ ! -z "$line" ]] && array+=("$line")
done; <<< $(find . -name ${input} -print)


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


i=0


while [ $i -lt $len ]
do
echo ${array[$i]}
let i++
done