文件名包含空格时如何使用find?


17

我想将文件名传递给其他程序,但是当文件名中包含空格时,它们都会阻塞。

假设我有一个名为的文件。

foo bar

如何获得find正确的名字?

显然我想要:

foo\ bar

要么:

"foo bar"

编辑:我不想通过xargs,我想从中获取正确格式的字符串,find以便可以将文件名的字符串直接传递到另一个程序。


5
您要用什么管道?您知道-exec带有的标志find吗?您可以通过执行此操作-exec而不是通过管道将其传递给其他命令来减轻此错误并使命令更有效。我的$ .02
h3rrmiller

6
@bug:find格式化文件名就好了;他们每行写一个名字。(当然,如果文件名包含换行符,这是模棱两可的。)因此问题是接收端在收到空格时会“窒息”,这意味着如果您想要一个有意义的答案,则必须告诉我们接收端是什么。
rici

2
您所谓的“正确格式化”实际上是“由外壳转义以供使用”。多数可以读取一堆文件名的实用程序都会使用shell转义的名称,但是实际上(例如)find提供一种选项来以适合shell的格式输出文件名是有意义的。通常,尽管如此,-print0GNU find扩展也可以在许多其他情况下正常工作,并且您应该学会在任何情况下都使用它。
三胞胎

2
@bug:顺便说一下,ls $(command...)不会通过来馈送列表stdin。它将的输出$(command...)直接放入命令行。在这种情况下,就是从c读取的shell,它将使用的当前值$IFS来决定如何对输出进行字拆分。通常,最好使用xargs。您不会注意到性能下降。
rici

2
find -printf '"%p"\n'将在找到的每个名称周围添加双引号,但不会在文件名中正确引用任何双引号。如果您的文件名没有任何嵌入的双引号,则可以忽略该问题:或通过管道sed 's/"/&&/g;s/^""/"/;s/""$/"/'。如果您的文件名最终由外壳程序处理,则您可能应该使用单引号而不是双引号(否则sweet$HOME将变成sheet/home/you)。对于带有换行符的文件名,这仍然不是很可靠。您要如何处理这些?
Tripleee

Answers:


18

正确地:

find . -type f -exec sh -c '
  for f do
    : command "$f"
  done
' sh {} +

find支持-print0xargs支持-0

find . -type f -print0 | xargs -0 <command>

-0 选项告诉xargs使用ASCII NUL字符而不是空格来结束(分隔)文件名。

例:

find . -maxdepth 1 -type f -print0 | xargs -0 ls -l

不起作用 当我跑步时,ls $(find . -maxdepth 1 -type f -print0 | xargs -0)我会收到 ls: cannot access ./foo: No such file or directory ls: cannot access bar: No such file or directory
2013年

1
您是否按照Gnouc实际的方式尝试过它?如果您坚持以自己的方式进行操作,请尝试将$(..)双引号括起来"$(..)"
evilsoup

3
@bug:您的命令是错误的。尝试正是我又写道和阅读的手册页findxargs
cuonglm

我知道了,然后我又想获得一个可以直接通过管道传递的格式化字符串。
错误

1
@bug:只需使用xargs -0 <您的程序>
cuonglm

10

使用-print0是一种选择,但不是所有的程序中使用nullbyte分隔的数据流,所以你必须使用支持xargs-0,如Gnouc的回答所述某些方面选项。

另一种方法是在使用find-exec-execdir选项。以下第一个将文件名somecommand一次输入一个,而第二个将扩展到文件列表:

find . -type f -exec somecommand '{}' \;
find . -type f -exec somecommand '{}' +

您可能会发现,在许多情况下,使用globing更好。如果您使用的是现代外壳(bash 4 +,zsh,ksh),则可以使用globstar**)进行递归遍历。在bash中,您必须设置以下内容:

shopt -s globstar
somecommand ./**/*.txt ## feeds all *.txt files to somecommand, recursively

我的shopt -s globstar extglob.bashrc中有一行内容,因此始终为我启用了此功能(扩展的glob也是如此,这也很有用)。

如果您不希望递归,那么显然可以改用./*.txt使用工作目录中的每个* .txt。find具有一些非常有用的细粒度搜索功能,并且对于成千上万个文件是必需的(此时,您将遇到shell的最大参数数量),但是对于日常使用而言,则通常是不必要的。


嘿@evilsoup {}在此脚本中做什么?
Ayusman '19

3

就个人而言,我将使用-execfind动作来解决此类问题。或者,如有必要,xargs它允许并行执行。

但是,有一种方法可以find生成bash可读的文件名列表。毫不奇怪,它使用-execbash特别是printf命令的扩展:

find ... -exec bash -c 'printf "%q " "$@"' printf {} ';'

但是,尽管那样可以正确打印出用shell进行转义的单词,但是它不能与一起使用$(...),因为$(...)它不会解释引号或转义符。($(...)除非保留引号,否则其结果会进行分词和路径名扩展。)因此,以下操作将无法满足您的要求:

ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)

您将要做的是:

eval "ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)"

(请注意,我并未尝试测试上述怪异行为。)

但是,您也可以这样做:

find ... -exec ls {} +

我认为该ls场景无法充分描述OP的用例,但这只是推测,因为我们没有看到他实际上试图完成的工作。该解决方案实际上效果很好。对于我尝试过的所有有趣的文件名,我都得到了(模糊地)期望的输出,包括touch "$(tr a-z '\001-\026' <<<'the quick brown fox jumped over the lazy dogs')"
Tripleee

@triplee:我也不知道OP想要做什么。构造要传递给加引号的字符串的唯一真正好处eval是,您不必将其传递给它eval。您可以将其保存在参数中,以后再用不同的命令使用几次。但是,OP并没有迹象表明这是用例(如果确实如此,则将文件名放入数组中可能会更好,尽管这也很棘手。)
rici


-2
    find . -type f -name \*\  | sed -e 's/ /<thisisspace>/g'

这是一个有趣的回答,但这不是对这个问题的答案。
斯科特,
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.