捕获find的输出。 -print0到一个bash数组中

使用find . -print0 find . -print0似乎是获得bash文件列表的唯一安全方式,因为文件名可能包含空格,换行符,引号等。

但是,我很难在bash或其他命令行工具中使find的输出有用。 我设法使用输出的唯一方法是通过pipe道到Perl,并将Perl的IFS更改为空:

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

这个例子打印find的文件数量,避免文件名中换行符破坏计数的危险,就像下面这样:

 find . | wc -l 

由于大多数命令行程序不支持空分界的input,我认为最好的办法是捕获find . -print0的输出find . -print0 find . -print0在bash数组中,就像我在上面的perl代码片段中所做的那样,然后继续执行任务,不pipe它是什么。

我怎样才能做到这一点?

这不起作用:

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

一个更普遍的问题可能是: 我如何用bash中的文件列表来做有用的事情?

Greg的BashFAQ无耻地偷走了:

 unset ai 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) 

请注意,此处使用的redirect结构( cmd1 < <(cmd2) )与更常用的pipe道( cmd2 | cmd1 )相似,但不完全相同 – 如果命令是shell内build的(例如while )在子壳中执行它们,并且它们设置的任何variables(例如数组a )在退出时都会丢失。 cmd1 < <(cmd2)只在一个子shell中运行cmd2,所以数组超出了它的构造。 警告:这种forms的redirect只能在bash中使用,甚至在sh-emulation模式下不能使用bash。 你必须用#!/bin/bash启动你的脚本。

此外,由于文件处理步骤(在这种情况下,只是a[i++]="$file" ,但你可能想在循环中直接做一些更奇特的事情)的inputredirect,它不能使用任何可能读取标准input。 为了避免这个限制,我倾向于使用:

 unset ai 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) 

…通过单元3传递文件列表,而不是标准input。

也许你正在寻找xargs:

 find . -print0 | xargs -r0 do_something_useful 

选项-L 1对你也可能是有用的,这使得xargs执行do_something_useful只有一个文件参数。

主要的问题是,分隔符NUL(\ 0)在这里是无用的,因为不可能为IFS分配一个NUL值。 所以作为优秀的程序员,我们保重,我们程序的input是它能够处理的东西。

首先我们创build一个小程序,为我们做这个部分:

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

…并称之为base64str(不要忘记chmod + x)

其次,我们现在可以使用一个简单而直接的for循环:

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

所以诀窍是,一个base64string没有任何迹象,为bash造成麻烦 – 当然一个xxd或类似的东西也可以做这个工作。

这里有一篇关于如何在shell中正确处理文件名的文章,有很多细节:

http://www.dwheeler.com/essays/filenames-in-shell.html

计数文件的另一种方法是:

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

我认为更优雅的解决scheme存在,但我会折腾这一个。这也适用于空格和/或换行符的文件名:

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

然后你可以例如逐个列出文件(在这种情况下以相反的顺序):

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

这个页面提供了一个很好的例子,更多的请看高级Bash脚本指南中的第26章 。

你可以安全地做这个计数:

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

(它为每个find的文件/目录打印换行符,然后计算打印出来的换行符)

如果可以,请避免使用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' 

我是新的,但我相信这是一个答案。 希望它有助于某人:

 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 

这与Stephan202的版本相似,但文件(和目录)一次放入数组中。 这里的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 

为了得到一个计数:

 echo ${#files[@]} 

老问题,但没有人提出这个简单的方法,所以我想我会的。 当然,如果你的文件名有一个ETX,这并不能解决你的问题,但我怀疑它适用于任何真实世界的场景。 尝试使用null似乎违反了默认的IFS处理规则。 根据你的口味寻找select和error handling。

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

戈登·戴维森(Gordon Davisson)的回答是很棒的。 但是对于zsh用户来说有一个有用的捷径:

首先,把你的string放在一个variables中:

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

接下来,拆分这个variables并将其存储在一个数组中:

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

有一个技巧: ^@是NUL字符。 要做到这一点,你必须先按Ctrl + V再按Ctrl + @。

您可以检查$ B的每个条目是否包含正确的值:

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

仔细的读者可能注意到,在大多数情况下,使用**语法可以避免调用find命令。 例如:

 B=( /tmp/** ) 

自Bash 4.4以来,内buildmapfile-d开关(用于指定分隔符,类似于read语句的-d开关),分隔符可以是空字节。 因此,标题中的问题是一个很好的答案

捕获find . -print0输出find . -print0 find . -print0到一个bash数组中

是:

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

Bash从来没有擅长处理文件名(或任何真正的文本),因为它使用空格作为列表分隔符。

我build议与sh库一起使用Python。