如何在 Bash 中将“查找”命令结果存储为数组
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23356779/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I store the "find" command results as an array in Bash
提问by Juneyoung Oh
I am trying to save the result from find
as arrays.
Here is my code:
我正在尝试将结果保存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
I get 2 .txt files under current directory.
So I expect '2' as result of ${len}
. However, it prints 1.
The reason is that it takes all result of find
as one elements.
How can I fix this?
我在当前目录下得到 2 个 .txt 文件。所以我希望 '2' 作为${len}
. 但是,它打印 1。原因是它将所有结果find
作为一个元素。我怎样才能解决这个问题?
P.S
I found several solutions on StackOverFlow about a similar problem. However, they are a little bit different so I can't apply in my case. I need to store the results in a variable before the loop. Thanks again.
PS
我在 StackOverFlow 上找到了几个关于类似问题的解决方案。但是,它们有点不同,所以我不能在我的情况下申请。我需要在循环之前将结果存储在一个变量中。再次感谢。
回答by John1024
Update 2020 for Linux Users:
适用于 Linux 用户的 2020 年更新:
If you have an up-to-date version of bash (4.4-alpha or better), as you probably do if you are on Linux, then you should be using Benjamin W.'s answer.
如果您有最新版本的 bash(4.4-alpha 或更高版本),就像您在 Linux 上所做的那样,那么您应该使用Benjamin W.'s answer。
If you are on Mac OS, which —last I checked— still used bash 3.2, or are otherwise using an older bash, then continue on to the next section.
如果您在 Mac OS 上——我上次检查过——仍然使用 bash 3.2,或者正在使用旧的 bash,然后继续下一部分。
Answer for bash 4.3 or earlier
bash 4.3 或更早版本的答案
Here is one solution for getting the output of find
into a bash
array:
这是将 的输出find
放入bash
数组的一种解决方案:
array=()
while IFS= read -r -d $'array=()
find . -name "${input}" -print0 >tmpfile
while IFS= read -r -d $'set +m
shopt -s lastpipe
array=()
find . -name "${input}" -print0 | while IFS= read -r -d $'array=`find . -name "${input}"`
'; do array+=("$REPLY"); done; declare -p array
'; do
array+=("$REPLY")
done <tmpfile
rm -f tmpfile
'; do
array+=("$REPLY")
done < <(find . -name "${input}" -print0)
This is tricky because, in general, file names can have spaces, new lines, and other script-hostile characters. The only way to use find
and have the file names safely separated from each other is to use -print0
which prints the file names separated with a null character. This would not be much of an inconvenience if bash's readarray
/mapfile
functions supported null-separated strings but they don't. Bash's read
does and that leads us to the loop above.
这很棘手,因为通常文件名可以包含空格、换行符和其他脚本敌对字符。使用find
并安全地将文件名彼此分开的唯一方法是使用-print0
which 打印用空字符分隔的文件名。如果 bash 的readarray
/mapfile
函数支持空分隔的字符串,但它们不支持,这不会带来太大的不便。Bashread
确实如此,这将我们引向上面的循环。
[This answer was originally written in 2014. If you have a recent version of bash, please see the update below.]
【这个答案最初写于2014年,如果你有最新版本的bash,请看下面的更新。】
How it works
这个怎么运作
The first line creates an empty array:
array=()
Every time that the
read
statement is executed, a null-separated file name is read from standard input. The-r
option tellsread
to leave backslash characters alone. The-d $'\0'
tellsread
that the input will be null-separated. Since we omit the name toread
, the shell puts the input into the default name:REPLY
.The
array+=("$REPLY")
statement appends the new file name to the arrayarray
.The final line combines redirection and command substitution to provide the output of
find
to the standard input of thewhile
loop.
第一行创建一个空数组:
array=()
每次
read
执行该语句时,都会从标准输入中读取一个以空分隔的文件名。该-r
选项告诉read
单独保留反斜杠字符。该-d $'\0'
通知read
的投入将是空分隔。由于我们省略了名称 toread
,shell 将输入放入默认名称:REPLY
。该
array+=("$REPLY")
语句将新文件名附加到数组array
。最后一行结合了重定向和命令替换,以将 的输出提供
find
给while
循环的标准输入。
Why use process substitution?
为什么要使用进程替换?
If we didn't use process substitution, the loop could be written as:
如果我们不使用进程替换,循环可以写成:
array=(`find . -name "${input}"`) # don't do this
In the above the output of find
is stored in a temporary file and that file is used as standard input to the while loop. The idea of process substitution is to make such temporary files unnecessary. So, instead of having the while
loop get its stdin from tmpfile
, we can have it get its stdin from <(find . -name ${input} -print0)
.
在上面的输出find
存储在一个临时文件中,该文件用作 while 循环的标准输入。进程替换的想法是使这些临时文件变得不必要。因此,不是让while
循环从 获取其标准输入tmpfile
,我们可以让它从 获取标准输入<(find . -name ${input} -print0)
。
Process substitution is widely useful. In many places where a command wants to readfrom a file, you can specify process substitution, <(...)
, instead of a file name. There is an analogous form, >(...)
, that can be used in place of a file name where the command wants to writeto the file.
过程替换非常有用。在命令要从文件中读取的许多地方,您可以指定进程替换<(...)
,而不是文件名。有一种类似的形式 ,>(...)
可用于代替命令要写入文件的文件名。
Like arrays, process substitution is a feature of bash and other advanced shells. It is not part of the POSIX standard.
与数组一样,进程替换是 bash 和其他高级 shell 的一个特性。它不是 POSIX 标准的一部分。
Alternative: lastpipe
替代品:最后一根烟斗
If desired, lastpipe
can be used instead of process substitution (hat tip: Caesar):
如果需要,lastpipe
可以使用代替过程替换(帽子提示:Caesar):
mapfile -d $'readarray -d '' array < <(find . -name "$input" -print0)
' array < <(find . -name "${input}" -print0)
shopt -s lastpipe
tells bash to run the last command in the pipeline in the current shell (not the background). This way, the array
remains in existence after the pipeline completes. Because lastpipe
only takes effect if job control is turned off, we run set +m
. (In a script, as opposed to the command line, job control is off by default.)
shopt -s lastpipe
告诉 bash 在当前 shell(不是后台)中运行管道中的最后一个命令。这样,array
管道完成后仍然存在。因为lastpipe
只有在关闭作业控制时才会生效,所以我们运行set +m
. (在脚本中,与命令行相反,作业控制默认是关闭的。)
Additional notes
补充说明
The following command creates a shell variable, not a shell array:
以下命令创建一个 shell 变量,而不是一个 shell 数组:
mapfile [-d delim] [array]
If you wanted to create an array, you would need to put parens around the output of find. So, naively, one could:
如果你想创建一个数组,你需要在 find 的输出周围放置括号。因此,天真地,人们可以:
mapfile [-d delim] [array]
The problem is that the shell performs word splitting on the results of find
so that the elements of the array are not guaranteed to be what you want.
问题是 shell 对结果进行分词,find
因此不能保证数组的元素是你想要的。
Update 2019
2019 年更新
Starting with version 4.4-alpha, bash now supports a -d
option so that the above loop is no longer necessary. Instead, one can use:
从 4.4-alpha 版本开始,bash 现在支持一个-d
选项,因此不再需要上述循环。相反,可以使用:
shopt -s globstar nullglob
array=( **/*"$input"* )
For more information on this, please see (and upvote) Benjamin W.'s answer.
有关这方面的更多信息,请参阅(并投票)Benjamin W. 的回答。
回答by Benjamin W.
Bash 4.4 introduced a -d
option to readarray
/mapfile
, so this can now be solved with
Bash 4.4-d
为readarray
/引入了一个选项mapfile
,因此现在可以通过
array=(`find . -type f | sort -r | head -2`)
for a method that works with arbitrary filenames including blanks, newlines, and globbing characters. This requires that your find
supports -print0
, as for example GNU find does.
用于处理任意文件名的方法,包括空格、换行符和通配符。这需要您的find
支持-print0
,例如 GNU find 所做的。
From the manual(omitting other options):
从手册(省略其他选项):
IFS='\n' read -r -a txt_files <<< $(find /path/to/dir -name "*.txt")
-d
The first character ofdelim
is used to terminate each input line, rather than newline. Ifdelim
is the empty string,mapfile
will terminate a line when it reads a NUL character.
#!/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
-d
的第一个字符delim
用于终止每个输入行,而不是换行符。如果delim
是空字符串,mapfile
当它读取一个 NUL 字符时将终止一行。
And readarray
is just a synonym of mapfile
.
并且readarray
只是 的同义词mapfile
。
回答by chepner
If you are using bash
4 or later, you can replace your use of find
with
如果您使用bash
4或更高版本,您可以取代你使用的find
有
declare -a names=$(echo "("; find <path> <other options> -printf '"%p" '; echo ")")
for nm in "${names[@]}"
do
echo "$nm"
done
The **
pattern enabled by globstar
matches 0 or more directories, allowing the pattern to match to an arbitrary depth in the current directory. Without the nullglob
option, the pattern (after parameter expansion) is treated literally, so with no matches you would have an array with a single string rather than an empty array.
**
启用的模式globstar
匹配 0 个或多个目录,允许模式匹配当前目录中的任意深度。如果没有该nullglob
选项,则模式(在参数扩展后)按字面处理,因此如果没有匹配项,您将拥有一个包含单个字符串的数组,而不是一个空数组。
Add the dotglob
option to the first line as well if you want to traverse hidden directories (like .ssh
) and match hidden files (like .bashrc
) as well.
dotglob
如果您还想遍历隐藏目录(如.ssh
)并匹配隐藏文件(如.bashrc
),也将该选项添加到第一行。
回答by Ahmed Al-Haffar
you can try something like
你可以尝试类似的东西
##代码## ,为了打印数组值,您可以尝试类似 echo"${array[*]}"
"${array[*]}"
回答by rashok
In bash, $(<any_shell_cmd>)
helps to run a command and capture the output. Passing this to IFS
with \n
as delimiter helps to convert that to an array.
在 bash 中,$(<any_shell_cmd>)
有助于运行命令并捕获输出。将此传递给IFS
with \n
as 分隔符有助于将其转换为数组。
回答by user1357768
You could do like this:
你可以这样做:
##代码##回答by R Risack
For me, this worked fine on cygwin:
对我来说,这在 cygwin 上运行良好:
##代码##This works with spaces, but not with double quotes (") in the directory names (which aren't allowed in a Windows environment anyway).
这适用于空格,但不适用于目录名称中的双引号 (")(无论如何在 Windows 环境中是不允许的)。
Beware the space in the -printf option.
注意 -printf 选项中的空格。