文件名中包含空格时,如何解析find命令的输出?


12

使用诸如

for i in `find . -name \*.txt` 

如果某些文件名中包含空格,则将中断。

我可以使用什么技术来避免此问题?


1
请注意,文件的文件名中也可以包含换行符。这就是为什么find -print0和的原因xargs -0
丹尼尔·贝克

Answers:


12

理想情况下,您根本不会那样做,因为在shell脚本中正确地解析文件名总是很困难的(将其修复为空格,其他嵌入式字符(尤其是换行符)仍然会遇到问题)。这甚至被列为BashPitfalls页面中的第一项

也就是说,有一种方法可以几乎完成您想要的操作:

oIFS=$IFS
IFS=$'\n'

find . -name '*.txt' | while read -r i; do
  # use "$i" with whatever you're doing
done

IFS=$oIFS

记住$i在使用它时也要引用,以避免以后其他事情解释空格。还请记住$IFS在使用后重新设置,因为不这样做会在以后引起令人困惑的错误。

这确实附加了另一个警告:while循环内发生的事情可能发生在子shell中,具体取决于您所使用的shell,因此变量设置可能不会持久。在for这一点,但在价格,即使你的应用循环版本避免$IFS的解决方案,以避免问题与空间,然后你会惹上麻烦,如果find返回的文件太多。

在某种程度上,所有这些的正确解决方案变为使用Perl或Python等语言而不是Shell进行。


1
我喜欢仅使用Python避免所有这些的想法。
Scott C Wilson

12

使用find -print0并将其输送到xargs -0,或者编写您自己的小C程序并将其输送到小C程序。这是-print0-0被发明。

Shell脚本并不是处理带有空格的文件名的最佳方法:您可以做到,但是它很笨拙。


在我的机器上可以运行^ TM!
mcandre

2

您可以将“内部字段分隔符”(IFS)设置为用于循环参数拆分的空格以外的其他值,例如

ORIGIFS=${IFS}
NL='
'
IFS=${NL}
for i in $(find . -name '*.txt'); do
    IFS=${ORIGIFS}
    #do stuff
done
IFS=${ORIGIFS}

IFS认为它在find中使用后会重置,主要是因为它看起来不错。将其设置为换行符,我还没有发现任何问题,但是我认为这是“更干净的”。

根据您要对输出进行的处理find,另一种方法是直接-execfind命令一起使用,或者使用-print0并将其通过管道传递到xargs -0。在第一种情况下find,请注意文件名的转义。在这种-print0情况下,find使用空分隔符打印其输出,然后xargs在此分隔。由于没有文件名可以包含该字符(我所知道的),因此这始终是安全的。这在简单情况下最有用;通常不是完整for循环的理想替代品。


1

使用find -print0xargs -0

采用find -print0联合xargs -0反对合法的文件名完全健壮,并且是可扩展性最强的方法之一。例如,假设您要列出当前目录中的每个PDF文件。你可以写

$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 echo

这将-iname '*.pdf'在当前目录(.)和任何子目录中找到(通过)每个PDF,并将每个PDF 作为参数传递给echo命令。因为我们指定了该-n 1选项,xargs所以一次只能将一个参数传递给echo。如果我们省略了该选项,xargs则应尽可能多地传递给echo。(您可以echo short input | xargs --show-limits查看命令行中允许多少字节。)

到底是xargs做什么的?

我们可以清楚地看到使用输入脚本xargs的效果,-n特别是通过输入的脚本,该脚本以比更为精确的方式回显其参数echo

$ cat > echoArgs.sh <<'EOF'
#!/bin/bash
echo "Number of arguments: $#"

[[ $# -eq 0 ]] && exit

for i in $(seq 1 $#); do
    echo "Arg $i: <$1>"
    shift
done
EOF

$ find . -iname '*.pdf' -print0 | xargs -0 ./echoArgs.sh
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 ./echoArgs.sh

请注意,它可以很好地处理空格和换行符,

$ touch 'A space-age
new line of vending machines.pdf'
$ find . -iname '*space*' -print0 | xargs -0 -n 1 ./echoArgs.sh

以下常见解决方案尤其麻烦:

chmod +x ./echoArgs.sh
for file in $(ls *spacey*); do
  ./echoArgs.sh "$file"
done
笔记

1

我不同意bashbasher,因为bash与* nix工具集一起,它非常擅长处理文件(包括名称中嵌入了空格的文件)。

实际上,find您可以选择要处理的文件进行精细控制。在bash方面,您实际上只需要意识到必须将字符串放入bash words;即可。通常使用“双引号”或其他一些机制(例如使用IFS或find的{}

请注意,在大多数情况下,您无需设置和重置IFS;只需在本地使用IFS,如下面的示例所示。所有这三个都可以处理空格。同样,您也不需要“标准”循环结构,因为find的 \; 实际上是一个循环。只需将循环逻辑放入bash函数中(如果您未调用标准工具)。

IFS=$'\n' find ~/ -name '*.txt' -exec  function-or-util {} \;  

还有两个例子

IFS=$'\n' find ~/ -name '*.txt' -exec  printf 'Hello %s\n' {} \;  
IFS=$'\n' find ~/ -name '*.txt' -exec  echo {} \+ |sed 's/home//'  

'找到also allows you to pass multiple filenames as args to you script ..(if it suits your need: use+ instead\;`)


1
两种观点都有一定的道理。当我只处理自己的文件时,我只会使用find而不必担心,因为我的文件名称中没有空格(或回车!)。但是,当您开始使用其他人的文件时,必须使用更可靠的技术。
Scott C Wilson
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.