简体   繁体   English

捕获查找的输出。 -print0进入bash数组

[英]Capturing output of find . -print0 into a bash array

Using find . -print0 使用find . -print0 find . -print0 seems to be the only safe way of obtaining a list of files in bash due to the possibility of filenames containing spaces, newlines, quotation marks etc. find . -print0似乎是获取bash中文件列表的唯一安全方法,因为文件名可能包含空格,换行符,引号等。

However, I'm having a hard time actually making find's output useful within bash or with other command line utilities. 但是,我很难让find的输出在bash或其他命令行实用程序中有用。 The only way I have managed to make use of the output is by piping it to perl, and changing perl's IFS to null: 我设法使用输出的唯一方法是将它传递给perl,并将perl的IFS更改为null:

find . -print0 | perl -e '$/="\0"; @files=<>; print $#files;'

This example prints the number of files found, avoiding the danger of newlines in filenames corrupting the count, as would occur with: 此示例打印找到的文件数,避免文件名中的换行符损坏计数的危险,如下所示:

find . | wc -l

As most command line programs do not support null-delimited input, I figure the best thing would be to capture the output of find . -print0 由于大多数命令行程序不支持空分隔输入,我认为最好的方法是捕获find . -print0的输出find . -print0 find . -print0 in a bash array, like I have done in the perl snippet above, and then continue with the task, whatever it may be. bash数组中的find . -print0 ,就像我在上面的perl片段中所做的那样,然后继续执行任务,无论它是什么。

How can I do this? 我怎样才能做到这一点?

This doesn't work: 这不起作用:

find . -print0 | ( IFS=$'\0' ; array=( $( cat ) ) ; echo ${#array[@]} )

A much more general question might be: How can I do useful things with lists of files in bash? 一个更普遍的问题可能是: 如何用bash中的文件列表做有用的事情?

Shamelessly stolen from Greg's BashFAQ : Greg的BashFAQ无耻地偷走了:

unset a i
while IFS= read -r -d $'\0' file; do
    a[i++]="$file"        # or however you want to process each file
done < <(find /tmp -type f -print0)

Note that the redirection construct used here ( cmd1 < <(cmd2) ) is similar to, but not quite the same as the more usual pipeline ( cmd2 | cmd1 ) -- if the commands are shell builtins (eg while ), the pipeline version executes them in subshells, and any variables they set (eg the array a ) are lost when they exit. 请注意,此处使用的重定向构造( cmd1 < <(cmd2) )与更常用的管道( cmd2 | cmd1 )类似但不完全相同 - 如果命令是shell builtins(例如while ),则管道版本在子shell中执行它们,它们设置的任何变量(例如数组a )在退出时都会丢失。 cmd1 < <(cmd2) only runs cmd2 in a subshell, so the array lives past its construction. cmd1 < <(cmd2)仅在子shell中运行cmd2,因此数组将超过其构造。 Warning: this form of redirection is only available in bash, not even bash in sh-emulation mode; 警告:这种重定向形式仅在bash中可用,在sh仿真模式下甚至不是bash; you must start your script with #!/bin/bash . 你必须用#!/bin/bash开始你的脚本。

Also, because the file processing step (in this case, just a[i++]="$file" , but you might want to do something fancier directly in the loop) has its input redirected, it cannot use any commands that might read from stdin. 另外,因为文件处理步骤(在这种情况下,只是a[i++]="$file" ,但你可能想在循环中直接做一些事情)它的输入被重定向,它不能使用任何可能从中读取的命令标准输入。 To avoid this limitation, I tend to use: 为了避免这种限制,我倾向于使用:

unset a i
while IFS= read -r -u3 -d $'\0' file; do
    a[i++]="$file"        # or however you want to process each file
done 3< <(find /tmp -type f -print0)

...which passes the file list via unit 3, rather than stdin. ...通过单元3传递文件列表,而不是stdin。

Maybe you are looking for xargs: 也许你正在寻找xargs:

find . -print0 | xargs -r0 do_something_useful

The option -L 1 could be useful for you too, which makes xargs exec do_something_useful with only 1 file argument. 选项-L 1对你也很有用,这使得xargs exec do_something_useful只有1个文件参数。

The main problem is, that the delimiter NUL (\\0) is useless here, because it isn't possible to assign IFS a NUL-value. 主要问题是,分隔符NUL(\\ 0)在这里没用,因为不可能为IFS分配NUL值。 So as good programmers we take care, that the input for our program is something it is able to handle. 因此,作为优秀的程序员,我们需要注意的是,我们程序的输入是它能够处理的。

First we create a little program, which does this part for us: 首先,我们创建一个小程序,为我们完成这一部分:

#!/bin/bash
printf "%s" "$@" | base64

...and call it base64str (don't forget chmod +x) ...并将其命名为base64str(不要忘记chmod + x)

Second we can now use a simple and straightforward for-loop: 其次,我们现在可以使用一个简单而直接的for循环:

for i in `find -type f -exec base64str '{}' \;`
do 
  file="`echo -n "$i" | base64 -d`"
  # do something with file
done

So the trick is, that a base64-string has no sign which causes trouble for bash - of course a xxd or something similar can also do the job. 所以诀窍是,base64-string没有任何迹象会导致bash出现问题 - 当然xxd或类似的东西也可以完成这项工作。

另一种计算文件的方法:

find /DIR -type f -print0 | tr -dc '\0' | wc -c 

Since Bash 4.4, the builtin mapfile has the -d switch (to specify a delimiter, similar to the -d switch of the read statement), and the delimiter can be the null byte. 从Bash 4.4开始,内置mapfile具有-d开关(用于指定分隔符,类似于read语句的-d开关),分隔符可以是空字节。 Hence, a nice answer to the question in the title 因此,对标题中的问题给出了很好的答案

Capturing output of find . -print0 捕获find . -print0输出find . -print0 find . -print0 into a bash array find . -print0进入bash数组

is: 是:

mapfile -d '' ary < <(find . -print0)

You can safely do the count with this: 你可以安全地计算:

find . -exec echo ';' | wc -l

(It prints a newline for every file/dir found, and then count the newlines printed out...) (它为找到的每个文件/目录打印换行符,然后计算打印出的换行符...)

I think more elegant solutions exists, but I'll toss this one in. This will also work for filenames with spaces and/or newlines: 我认为存在更优雅的解决方案,但是我会把它放进去。这也适用于带有空格和/或换行符的文件名:

i=0;
for f in *; do
  array[$i]="$f"
  ((i++))
done

You can then eg list the files one by one (in this case in reverse order): 然后,您可以逐个列出文件(在这种情况下以相反的顺序):

for ((i = $i - 1; i >= 0; i--)); do
  ls -al "${array[$i]}"
done

This page gives a nice example, and for more see Chapter 26 in the Advanced Bash-Scripting Guide . 此页面提供了一个很好的示例,有关更多示例,请参阅Advanced Bash-Scripting Guide中的第26章

Avoid xargs if you can: 如果可以,请避免使用xargs:

man ruby | less -p 777 
IFS=$'\777' 
#array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' \; 2>/dev/null) ) 
array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' + 2>/dev/null) ) 
echo ${#array[@]} 
printf "%s\n" "${array[@]}" | nl 
echo "${array[0]}" 
IFS=$' \t\n' 

I am new but I believe that this an answer; 我是新人,但我相信这是一个答案; hope it helps someone: 希望它可以帮助某人:

STYLE="$HOME/.fluxbox/styles/"

declare -a array1

LISTING=`find $HOME/.fluxbox/styles/ -print0 -maxdepth 1 -type f`


echo $LISTING
array1=( `echo $LISTING`)
TAR_SOURCE=`echo ${array1[@]}`

#tar czvf ~/FluxieStyles.tgz $TAR_SOURCE

This is similar to Stephan202's version, but the files (and directories) are put into an array all at once. 这与Stephan202的版本类似,但文件(和目录)一次性放入一个数组中。 The for loop here is just to "do useful things": 这里的for循环只是为了“做有用的事情”:

files=(*)                        # put files in current directory into an array
i=0
for file in "${files[@]}"
do
    echo "File ${i}: ${file}"    # do something useful 
    let i++
done

To get a count: 为了得到一个数:

echo ${#files[@]}

Old question, but no-one suggested this simple method, so I thought I would. 老问题,但没有人建议这个简单的方法,所以我想我会。 Granted if your filenames have an ETX, this doesn't solve your problem, but I suspect it serves for any real-world scenario. 如果你的文件名有ETX,这不能解决你的问题,但我怀疑它适用于任何真实场景。 Trying to use null seems to run afoul of default IFS handling rules. 尝试使用null似乎违反了默认的IFS处理规则。 Season to your tastes with find options and error handling. 通过查找选项和错误处理来满足您的口味。

savedFS="$IFS"
IFS=$'\x3'
filenames=(`find wherever -printf %p$'\x3'`)
IFS="$savedFS"

Gordon Davisson's answer is great for bash. Gordon Davisson的回答对bash很有帮助。 However a useful shortcut exist for zsh users: 但是zsh用户存在一个有用的快捷方式:

First, place you string in a variable: 首先,将字符串放在变量中:

A="$(find /tmp -type f -print0)"

Next, split this variable and store it in an array: 接下来,拆分此变量并将其存储在数组中:

B=( ${(s/^@/)A} )

There is a trick: ^@ is the NUL character. 有一个技巧: ^@是NUL角色。 To do it, you have to type Ctrl+V followed by Ctrl+@. 要做到这一点,你必须输入Ctrl + V,然后按Ctrl + @。

You can check each entry of $B contains right value: 您可以检查$ B的每个条目是否包含正确的值:

for i in "$B[@]"; echo \"$i\"

Careful readers may notice that call to find command may be avoided in most cases using ** syntax. 细心的读者可能会注意到在大多数情况下使用**语法可以避免调用find命令。 For example: 例如:

B=( /tmp/** )

Bash has never been good at handling filenames (or any text really) because it uses spaces as a list delimiter. Bash从未擅长处理文件名(或任何文本),因为它使用空格作为列表分隔符。

I'd recommend using python with the sh library instead. 我建议使用shthon代替sh库。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM