如何在 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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 00:48:57  来源:igfitidea点击:

How can I store the "find" command results as an array in Bash

arraysbashvariablesfind

提问by Juneyoung Oh

I am trying to save the result from findas 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 findas 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 findinto a basharray:

这是将 的输出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 findand have the file names safely separated from each other is to use -print0which prints the file names separated with a null character. This would not be much of an inconvenience if bash's readarray/mapfilefunctions supported null-separated strings but they don't. Bash's readdoes and that leads us to the loop above.

这很棘手,因为通常文件名可以包含空格、换行符和其他脚本敌对字符。使用find并安全地将文件名彼此分开的唯一方法是使用-print0which 打印用空字符分隔的文件名。如果 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

这个怎么运作

  1. The first line creates an empty array: array=()

  2. Every time that the readstatement is executed, a null-separated file name is read from standard input. The -roption tells readto leave backslash characters alone. The -d $'\0'tells readthat the input will be null-separated. Since we omit the name to read, the shell puts the input into the default name: REPLY.

  3. The array+=("$REPLY")statement appends the new file name to the array array.

  4. The final line combines redirection and command substitution to provide the output of findto the standard input of the whileloop.

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

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

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

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

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 findis 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 whileloop 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, lastpipecan 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 lastpipetells bash to run the last command in the pipeline in the current shell (not the background). This way, the arrayremains in existence after the pipeline completes. Because lastpipeonly 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 findso 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 -doption 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 -doption to readarray/mapfile, so this can now be solved with

Bash 4.4-dreadarray/引入了一个选项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 findsupports -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 of delimis used to terminate each input line, rather than newline. If delimis the empty string, mapfilewill 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 readarrayis just a synonym of mapfile.

并且readarray只是 的同义词mapfile

回答by chepner

If you are using bash4 or later, you can replace your use of findwith

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

declare -a names=$(echo "("; find <path> <other options> -printf '"%p" '; echo ")")
for nm in "${names[@]}"
do
    echo "$nm"
done

The **pattern enabled by globstarmatches 0 or more directories, allowing the pattern to match to an arbitrary depth in the current directory. Without the nullgloboption, 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 dotgloboption 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 IFSwith \nas delimiter helps to convert that to an array.

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

##代码##

回答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 选项中的空格。