使用find . -print0
似乎是获取bash中文件列表的唯一安全方法,因为文件名可能包含空格,换行符,引号等。
但是,我真的很难让find的输出在bash或其他命令行实用程序中有用。我设法使用输出的唯一方法是将它传递给perl,并将perl的IFS更改为null:
find . -print0 | perl -e '$/="\0"; @files=<>; print $#files;'
此示例打印找到的文件数,避免文件名中的换行符损坏计数的危险,如下所示:
find . | wc -l
由于大多数命令行程序不支持空分隔输入,我认为最好的方法是在bash数组中捕获find . -print0
的输出,就像我在上面的perl片段中所做的那样,然后无论它是什么,都要继续完成任务。
我该怎么做?
这不起作用:
find . -print0 | ( IFS=$'\0' ; array=( $( cat ) ) ; echo ${#array[@]} )
更常见的问题可能是:如何使用bash中的文件列表执行有用的操作?
答案 0 :(得分:97)
从Greg's 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)
请注意,此处使用的重定向构造(cmd1 < <(cmd2)
)与更常用的管道(cmd2 | cmd1
)类似,但不完全相同 - 如果命令是shell builtins(例如{{ 1}}),管道版本在子shell中执行它们,它们设置的任何变量(例如数组while
)在退出时都会丢失。 a
仅在子shell中运行cmd2,因此数组将超过其构造。警告:这种重定向形式仅在bash中可用,在sh仿真模式下甚至不是bash;您必须使用cmd1 < <(cmd2)
启动脚本。
另外,因为文件处理步骤(在这种情况下只是#!/bin/bash
,但你可能想在循环中直接做一些更好的事情)将其输入重定向,所以它不能使用任何可能从stdin读取的命令。为了避免这种限制,我倾向于使用:
a[i++]="$file"
...通过单元3传递文件列表,而不是stdin。
答案 1 :(得分:7)
也许您正在寻找xargs:
find . -print0 | xargs -r0 do_something_useful
选项-L 1对你也很有用,这使得xargs exec do_something_useful只有1个文件参数。
答案 2 :(得分:5)
主要问题是,分隔符NUL(\ 0)在这里没用,因为无法为IFS分配NUL值。因此,作为优秀的程序员,我们需要注意的是,我们程序的输入是它能够处理的。
首先我们创建一个小程序,为我们做这部分:
#!/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
所以诀窍是,base64-string没有任何标志,这会给bash带来麻烦 - 当然xxd或类似的东西也可以完成这项工作。
答案 3 :(得分:4)
另一种计算文件的方法:
find /DIR -type f -print0 | tr -dc '\0' | wc -c
答案 4 :(得分:3)
自Bash 4.4以来,内置mapfile
具有-d
开关(用于指定分隔符,类似于-d
语句的read
开关)和分隔符可以是空字节。因此,对标题中的问题给出了一个很好的答案
捕获
find . -print0
的输出到bash数组
是:
mapfile -d '' ary < <(find . -print0)
答案 5 :(得分:2)
你可以安全地计算:
find . -exec echo ';' | wc -l
(它为找到的每个文件/目录打印换行符,然后计算打印出的换行符...)
答案 6 :(得分:1)
我认为存在更优雅的解决方案,但我会把它放进去。这也适用于带有空格和/或换行符的文件名:
i=0;
for f in *; do
array[$i]="$f"
((i++))
done
然后你可以逐个列出文件(在这种情况下是相反的顺序):
for ((i = $i - 1; i >= 0; i--)); do
ls -al "${array[$i]}"
done
This page提供了一个很好的示例,有关详情,请参阅Chapter 26中的Advanced Bash-Scripting Guide。
答案 7 :(得分:1)
如果可以,请避免使用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'
答案 8 :(得分:1)
我是新人,但我相信这是一个答案;希望它可以帮助某人:
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
答案 9 :(得分:0)
这与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[@]}
答案 10 :(得分:0)
老问题,但没有人建议这个简单的方法,所以我想我会。如果你的文件名有ETX,这不能解决你的问题,但我怀疑它适用于任何真实场景。尝试使用null似乎违反了默认的IFS处理规则。通过查找选项和错误处理来满足您的口味。
savedFS="$IFS"
IFS=$'\x3'
filenames=(`find wherever -printf %p$'\x3'`)
IFS="$savedFS"
答案 11 :(得分:0)
戈登·戴维森的回答对于bash来说很棒。但是zsh用户存在一个有用的快捷方式:
首先,将字符串放在变量中:
A="$(find /tmp -type f -print0)"
接下来,拆分此变量并将其存储在数组中:
B=( ${(s/^@/)A} )
有一个技巧:^@
是NUL角色。要做到这一点,你必须输入Ctrl + V,然后按Ctrl + @。
您可以检查$ B的每个条目是否包含正确的值:
for i in "$B[@]"; echo \"$i\"
细心的读者可能会注意到,在大多数情况下,使用find
语法可以避免调用**
命令。例如:
B=( /tmp/** )
答案 12 :(得分:-1)
Bash从未善于处理文件名(或任何文本),因为它使用空格作为列表分隔符。
我建议将python与sh库一起使用。