如何使用xargs复制文件名带有空格和引号的文件?

253

我正在尝试复制一个目录下的一堆文件,其中一些文件名中有空格和单引号。 当我尝试使用findgrepxargs一起运行时,我会得到以下错误:

find .|grep "FooBar"|xargs -I{} cp "{}" ~/foo/bar
xargs: unterminated quote

有没有更加强大的使用 xargs 的建议?

我是在使用 BSD 版本的 xargs,系统为 Mac OS X 10.5.3(Leopard)。


2
GNU xargs 的错误信息对于包含单引号的文件名更有帮助: "xargs: unmatched single quote; by default quotes are special to xargs unless you use the -0 option". - Steve Jessop
3
GNU xargs也有--delimiter选项(-d)。尝试使用\n作为分隔符,这可以防止xargs将带空格的行分隔成多个单词/参数。 - MattBianco
22个回答

214

您可以将所有内容组合到单个find命令中:

find . -iname "*foobar*" -exec cp -- "{}" ~/foo/bar \;

这将处理包含空格的文件名和目录。您可以使用-name选项获取区分大小写的结果。

注意:传递给cp--标志可以防止其将以-开头的文件视为选项进行处理。


74
人们使用xargs,通常比每次传递一个参数调用1000次可执行文件更快,而是5次调用,每次传递200个参数。 - tzot
12
Chris Jester-Young的回答应该是那个"好答案"。顺便说一下,如果文件名以"-"开头,这个解决方案是不起作用的。至少,在cp之后需要加上"--"。 - Keltia
11
速度示例-- 在829个文件中,“find -exec”方法用了26秒,而“find -print0 | xargs --null”的方法只用了0.7秒。 显著差异。 - Peter Porter
7
晚来的评论,但无论如何,“xargs”并不是解决您所描述问题的必需品,“find”已经通过“-exec” “+”标点符号支持了它。 - jlliagre
3
不回答如何处理空格的问题。 - Ben Glasser
显示剩余8条评论

121

find . -print0 | grep --null 'FooBar' | xargs -0 ...

这个命令会在当前目录下找到所有包含'FooBar'的文件,并将它们传递给后面的命令进行处理。 grepxargs 都支持--null-0 参数,但我不确定 Leopard 系统是否支持该参数,不过在GNU系统中是可以使用的。


1
Leopard支持“-Z”(它是GNU grep),当然find(1)和xargs(1)也支持“-0”。 - Keltia
1
在OS X 10.9中,grep -{z|Z}的意思是“表现得像zgrep”(解压缩),而不是预期的“在每个文件名后打印一个零字节”。使用grep --null来实现后者。 - bassim
4
find . -name 'FooBar' -print0 | xargs -0 ... 这个命令有什么问题? - Quentin Pradet
1
@QuentinPradet 显然,对于像“FooBar”这样的固定字符串,“-name”或“-path”都可以正常工作。 OP指定使用grep,可能是因为他们想使用正则表达式过滤列表。 - C. K. Young
1
@Hi-Angel 这正是我使用 xargs -0 find -print0 结合的原因。后者会打印带有 NUL 终止符的文件名,前者以这种方式接收文件。为什么?Unix 中的文件名可以包含换行符,但不能包含 NUL 字符。 - C. K. Young
显示剩余6条评论

110

实现原帖所需的最简单方法是将分隔符从任何空格更改为仅使用行末字符,如下所示:

find whatever ... | xargs -d "\n" cp -t /var/tmp

5
这个答案简单有效,言简意赅:xargs的默认定界符集过于宽泛,需要缩小到OP想要的范围。我亲身经历过这种情况,今天在cygwin中做类似的事情时也遇到了这个问题。如果我看过xargs命令的帮助文档,可能会避免一些麻烦,但你的解决方案让我成功解决了这个问题。谢谢!(是的,OP使用的是MacOS上的BSD xargs,而我没有使用过,但我相信xargs的“-d”参数在所有版本中都存在)。 - Etienne Delavennat
8
回答不错,但在 Mac 上不起作用。相反,我们可以将 find 的输出管道传递给 sed -e 's_\(.*\)_"\1"_g' 命令,在文件名周围强制添加引号。 - ishahak
11
应该接受这个答案。问题是关于使用 xargs - Mohammad Alhashash
4
我收到了 "xargs: illegal option -- d" 的错误提示。 - nehem
1
值得指出的是,在许多*nix系统中,文件名可以包含换行符。在实际应用中,你不太可能遇到这种情况,但如果你在不受信任的输入上运行shell命令,这可能是一个问题。 - Soren Bjornstad
显示剩余2条评论

78
这种方法更加高效,因为它不会多次运行“cp”命令:
find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar

1
这对我没用。它试图将~/foo/bar复制到任何你找到的地方,但不是相反的。 - Shervin Asgari
14
据我所知,cp 命令的 -t 标志是 GNU 扩展,在 OS X 上不可用。但如果它可用,则会按照此答案所示的方式工作。请注意,这里不提供任何解释。 - metamatt
3
我使用Linux。感谢"-t"开关,这就是我缺失的东西 :-) - Vahid Pazirandeh
它对我有效!+1 票 - Tung

64

我遇到了同样的问题。这是我解决它的方式:

find . -name '*FoooBar*' | sed 's/.*/"&"/' | xargs cp ~/foo/bar

我使用了sed命令来将输入的每一行替换为相同的行,但是用双引号括起来。从sed的手册中可以看到:" ...在替换字符串中出现的 & 符号将被替换为与正则表达式相匹配的字符串..." -- 在这个例子中,是指整行,即.*

这解决了xargs: unterminated quote错误。


3
我在使用 Windows 操作系统和 GNUWin32,所以我需要使用 sed s/.*/\"&\"/ 命令才能使它正常工作。 - Pat
是的,但是假设这不会处理带有 " 的文件名 - 除非 sed 也引用引号? - artfulrobot
使用 sed 是巧妙的解决方案,而且不需要重新编写问题! - entonio

58

这种方法适用于Mac OS X v10.7.5(狮子):

find . | grep FooBar | xargs -I{} cp {} ~/foo/bar

我也测试了您发布的确切语法。在10.7.5上也能正常工作。


4
这个命令可以运行,但是手册上说 -I 隐含了 -L 1,也就是说 cp 命令会针对每个文件执行一次,速度非常慢。 - artfulrobot
xargs -J % cp % <destination dir> 在OSX上可能更有效率。 - Walker D
3
抱歉,但这是不正确的。首先,它会产生完全与TO想要避免的错误相同的错误。您必须使用find ... -print0xargs -0来解决xargs的“默认引号是特殊字符”的问题。其次,在传递给xargs的命令中通常使用'{}'而不是{}来保护空格和特殊字符。 - Andreas Spindler
3
抱歉 Andreas Spindler,我对 xargs 不是很熟悉,在一些实验之后找到了这行代码。根据大多数评论和点赞的人反馈,它似乎对他们有效。您能否详细说明它会产生哪种错误?另外,您能否发布您认为更正确的确切输入?谢谢。 - frediy
这是在我的MacOS 10.15上运行的xargs部分:xargs -0 -J % cp -v % /foo/bar - Phlogi

13

不要使用xargs。它是一个很好的程序,但在处理非常规情况时与find不兼容。

这里有一个便携式(POSIX)解决方案,即不需要findxargscp GNU特定扩展:

find . -name "*FooBar*" -exec sh -c 'cp -- "$@" ~/foo/bar' sh {} +

注意这里的结尾使用 + 而不是更常见的 ;

该解决方案:

  • 可以正确地处理嵌入空格、换行符或其他特殊字符的文件和目录。

  • 适用于任何Unix和Linux系统,即使没有提供GNU工具包。

  • 不使用xargs,这是一个很好而且有用的程序,但需要太多调整和非标准功能才能正确处理find的输出。

  • 与已接受的答案和大部分甚至所有其他答案相比,也更有效率(读作更快)。

还要注意,尽管其他回复或评论中引用了 {},但它是无用的(除非您使用奇特的 fish shell)。


为什么它更快?[用户tzot写道:“人们使用xargs,因为通常调用可执行文件5次,每次使用200个参数比每次使用一个参数调用它1000次要快。”] - Peter Mortensen
1
@PeterMortensen 你可能忽略了结尾的加号。find可以在没有任何开销的情况下完成xargs所做的工作。 - jlliagre

11

对于那些依赖命令而不是find的人:

find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar

1
工作正常,但速度较慢,因为“-I”意味着“-L 1”。 - artfulrobot
唯一在2023年对我有效的答案。将换行符替换为\0。简单、聪明、有效。 - undefined

8

建议使用find命令的-print0选项,并与xargs命令的--null选项一起使用。


6
find | perl -lne 'print quotemeta' | xargs ls -d

我相信除了换行符之外,这种方法可以可靠地处理任何字符(如果您的文件名中有换行符,则可能存在更严重的问题)。它不需要GNU findutils,只需要Perl,因此几乎可以在任何地方使用。

文件名中是否可能有换行符?从未听说过。 - mtk
2
确实是这样。尝试一下,例如 mkdir test && cd test && perl -e 'open $fh, ">", "this-file-contains-a-\n-here"' && ls | od -tx1 - mavit
1
|perl -lne 'print quotemeta' 正是我一直在寻找的。这里的其他帖子并没有帮助我,因为我需要使用 grep -rl 而不是 find 来大大减少 PHP 文件的数量,只留下有恶意软件的文件。 - Marcos
Perl和quotemeta比print0/-0更通用 - 感谢提供处理带空格文件的通用解决方案。 - bmike

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接