捕获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中正确处理文件名的文章,有很多细节:
计数文件的另一种方法是:
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。