79

find . -print0由于文件名可能包含空格、换行符、引号等,因此using似乎是在 bash 中获取文件列表的唯一安全方法。

但是,我很难真正使 find 的输出在 bash 或其他命令行实用程序中有用。我设法利用输出的唯一方法是将其通过管道传输到 perl,并将 perl 的 IFS 更改为 null:

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

此示例打印找到的文件数,避免文件名中的换行符破坏计数的危险,如下所示:

find . | wc -l

由于大多数命令行程序不支持以 null 分隔的输入,我认为最好的办法是捕获find . -print0bash 数组中的输出,就像我在上面的 perl 片段中所做的那样,然后继续执行任务,不管它可能是。

我怎样才能做到这一点?

这不起作用:

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

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

4

13 回答 13

105

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)

请注意,此处使用的重定向构造 ( cmd1 < <(cmd2)) 与更常用的管道 ( ) 类似,但并不完全相同cmd2 | cmd1——如果命令是 shell 内置命令(例如while),则管道版本在子 shell 中执行它们,以及它们设置的任何变量(例如数组a)在退出时会丢失。 cmd1 < <(cmd2)仅在子外壳中运行 cmd2,因此该数组在其构造之后仍然存在。警告:这种形式的重定向只在 bash 中可用,甚至在 sh-emulation 模式下的 bash 也不可用;您必须以#!/bin/bash.

此外,由于文件处理步骤(在这种情况下,只是a[i++]="$file",但您可能想直接在循环中做一些更有趣的事情)将其输入重定向,它不能使用任何可能从标准输入读取的命令。为了避免这种限制,我倾向于使用:

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)

...它通过单元 3 传递文件列表,而不是标准输入。

于 2009-07-13T17:36:50.233 回答
10

从 Bash 4.4 开始,builtinmapfile有了-dswitch(指定分隔符,类似于语句的-dswitch read),分隔符可以是空字节。因此,标题中的问题的一个很好的答案

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

是:

mapfile -d '' ary < <(find . -print0)
于 2017-09-14T15:37:59.273 回答
7

也许您正在寻找 xargs:

find . -print0 | xargs -r0 do_something_useful

选项 -L 1 也可能对您有用,这使得 xargs exec do_something_useful 只需 1 个文件参数。

于 2009-07-12T22:08:17.550 回答
6

主要问题是,分隔符 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 字符串没有标志,这会给 bash 带来麻烦——当然,xxd 或类似的东西也可以完成这项工作。

于 2011-10-29T10:47:07.450 回答
4

另一种计算文件的方法:

find /DIR -type f -print0 | tr -dc '\0' | wc -c 
于 2009-07-13T06:49:58.927 回答
2

您可以安全地进行计数:

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

(它为找到的每个文件/目录打印一个换行符,然后计算打印出的换行符......)

于 2009-07-12T22:11:06.657 回答
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

此页面提供了一个很好的示例,更多信息请参见Advanced Bash-Scripting Guide中的第 26 章

于 2009-07-12T21:48:37.077 回答
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' 
于 2009-07-13T08:36:24.377 回答
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
于 2009-08-18T19:32:18.357 回答
1

Gordon Davisson 的回答非常适合 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/** )
于 2016-06-24T10:05:58.097 回答
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[@]}
于 2009-07-13T04:39:55.883 回答
0

老问题,但没有人建议这种简单的方法,所以我想我会的。如果你的文件名有 ETX,这并不能解决你的问题,但我怀疑它适用于任何现实世界的场景。尝试使用 null 似乎违反了默认的 IFS 处理规则。使用查找选项和错误处理根据您的口味调整。

savedFS="$IFS"
IFS=$'\x3'
filenames=(`find wherever -printf %p$'\x3'`)
IFS="$savedFS"
于 2016-02-13T02:24:43.590 回答
-1

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

我建议将 python 与sh库一起使用。

于 2013-01-06T13:14:29.643 回答