242

我正在尝试复制目录下的一堆文件,并且许多文件的名称中有空格和单引号。当我尝试将findgrepwith串在一起时xargs,出现以下错误:

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

关于更强大地使用 xargs 的任何建议?

这是在带有 BSD的Mac OS X 10.5.3xargs (Leopard) 上。

4

22 回答 22

206

您可以将所有这些组合到一个find命令中:

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

这将处理带有空格的文件名和目录。您可以使用-name来获得区分大小写的结果。

注意:--传递给的标志cp阻止它处理-以选项开头的文件。

于 2008-09-27T08:22:19.750 回答
122

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

我不知道在 Leopard 上是否grep支持--null,也不知道是否xargs支持-0,但在 GNU 上一切都很好。

于 2008-09-27T07:15:54.557 回答
101

做原始海报想要做的最简单的方法是将分隔符从任何空格更改为仅像这样的行尾字符:

find whatever ... | xargs -d "\n" cp -t /var/tmp
于 2015-11-04T17:21:16.440 回答
74

这更有效,因为它不会多次运行“cp”:

find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar
于 2008-09-29T14:50:21.390 回答
65

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

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

我曾经sed用同一行替换每一行输入,但用双引号括起来。在sed手册页中,“ ...替换中出现的 & 符号 (``&'') 被匹配 RE... 的字符串替换”——在这种情况下,.*是整行。

这解决了xargs: unterminated quote错误。

于 2012-08-22T05:57:43.863 回答
56

此方法适用于Mac OS X v10.7.5 (Lion):

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

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

于 2013-08-28T19:20:38.767 回答
13

只是不要使用xargs. find这是一个简洁的程序,但在面对非平凡的情况时它并不顺利。

find这是一个便携式(POSIX)解决方案,即不需要xargscpGNU 特定扩展的解决方案:

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

注意结尾+而不是更通常的;.

这个解决方案:

  • 正确处理带有嵌入空格、换行符或任何外来字符的文件和目录。

  • 适用于任何 Unix 和 Linux 系统,即使是那些不提供 GNU 工具包的系统。

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

  • 也比公认的和大多数(如果不是全部)其他答案更有效(阅读速度更快)。

另请注意,尽管在其他一些回复或评论中说明了什么,但引用{}是无用的(除非您使用的是异国情调的fish外壳)。

于 2013-06-26T16:41:06.867 回答
8

研究在 find 中使用带有 -print0 选项的 xargs 的 --null 命令行选项。

于 2008-09-27T07:16:20.203 回答
8

对于那些依赖命令而不是 find 的人,例如ls

find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar
于 2014-03-20T14:10:43.927 回答
6
find | perl -lne 'print quotemeta' | xargs ls -d

我相信这对于除换行符以外的任何字符都可以可靠地工作(我怀疑如果你的文件名中有换行符,那么你遇到的问题比这更糟糕)。它不需要 GNU findutils,只需要 Perl,所以它几乎可以在任何地方工作。

于 2012-02-09T22:23:51.527 回答
5

我发现以下语法对我很有效。

find /usr/pcapps/ -mount -type f -size +1000000c | perl -lpe ' s{ }{\\ }g ' | xargs ls -l | sort +4nr | head -200

在这个例子中,我在安装在“/usr/pcapps”的文件系统中寻找最大的 200 个文件,超过 1,000,000 字节。

“find”和“xargs”之间的 Perl 换行符转义/引用每个空白,因此“xargs”将任何带有嵌入空白的文件名作为单个参数传递给“ls”。

于 2009-01-23T22:39:33.383 回答
3

使用 Bash(不是 POSIX),您可以使用进程替换来获取变量中的当前行。这使您能够使用引号来转义特殊字符:

while read line ; do cp "$line" ~/bar ; done < <(find . | grep foo)
于 2014-01-01T13:09:45.457 回答
3

框架挑战——你在问如何使用 xargs。答案是:你不使用 xargs,因为你不需要它。

注释user80168描述了一种直接使用 cp 执行此操作的方法,无需为每个文件调用 cp :

find . -name '*FooBar*' -exec cp -t /tmp -- {} +

这有效,因为:

  • cp -t标志允许在 的开头附近给出目标目录cp,而不是在结尾附近。来自man cp
   -t, --target-directory=DIRECTORY
         copy all SOURCE arguments into DIRECTORY
  • --标志告诉将后面的所有内容解释为文件名,而不是标志,因此以或不混淆cp开头的文件; 你仍然需要这个,因为/字符由 解释,而任何其他特殊字符由 shell 解释。---cp---cp

  • find -exec command {} +变体本质上与 xargs 相同。来自man find

   -exec command {} +                                                     
         This  variant  of the -exec action runs the specified command on
         the selected files, but the command line is built  by  appending
         each  selected file name at the end; the total number of invoca‐
         matched  files.   The command line is built in much the same way
         that xargs builds its command lines.  Only one instance of  `{}'
         is  allowed  within the command, and (when find is being invoked
         from a shell) it should be quoted (for example, '{}') to protect
         it  from  interpretation  by shells.  The command is executed in
         the starting directory.  If any invocation  returns  a  non-zero
         value  as exit status, then find returns a non-zero exit status.
         If find encounters an error, this can sometimes cause an immedi‐
         ate  exit, so some pending commands may not be run at all.  This
         variant of -exec always returns true.

通过直接在 find 中使用它,这避免了管道或 shell 调用的需要,这样您就不必担心文件名中的任何讨厌的字符。

于 2019-06-28T08:43:05.630 回答
2

请注意,其他答案中讨论的大多数选项在不使用 GNU 实用程序的平台(例如 Solaris、AIX、HP-UX)上不是标准的。请参阅POSIX规范以了解“标准”xargs 行为。

我还发现 xargs 的行为,即使没有输入,它也会至少运行一次命令,这很麻烦。

我编写了自己的私有版本的 xargs (xargl) 来处理名称中的空格问题(只有换行符分开 - 尽管 'find ... -print0' 和 'xargs -0' 组合非常简洁,因为文件名不能包含 ASCII NUL '\0' 字符。我的 xargl 并不像值得发布的那样完整 - 特别是因为 GNU 具有至少一样好的设施。

于 2008-10-02T07:00:01.090 回答
2

对我来说,我试图做一些不同的事情。我想将我的 .txt 文件复制到我的 tmp 文件夹中。.txt 文件名包含空格和撇号字符。这适用于我的 Mac。

$ find . -type f -name '*.txt' | sed 's/'"'"'/\'"'"'/g' | sed 's/.*/"&"/'  | xargs -I{} cp -v {} ./tmp/
于 2015-10-08T01:58:36.057 回答
1

bill_starr 的 Perl 版本不适用于嵌入式换行符(仅处理空格)。对于那些在 Solaris 上没有 GNU 工具的用户,更完整的版本可能是(使用 sed)...

find -type f | sed 's/./\\&/g' | xargs grep string_to_find

根据需要调整 find 和 grep 参数或其他命令,但 sed 将修复嵌入的换行符/空格/制表符。

于 2009-06-17T01:53:47.073 回答
1

我使用了在 Solaris 上稍作修改的 Bill Star 的答案:

find . -mtime +2 | perl -pe 's{^}{\"};s{$}{\"}' > ~/output.file

这将在每一行周围加上引号。我没有使用“-l”选项,尽管它可能会有所帮助。

我要去的文件列表可能有'-',但没有换行符。我没有将输出文件与任何其他命令一起使用,因为我想在开始通过 xargs 大量删除它们之前查看找到的内容。

于 2010-10-31T17:15:31.670 回答
1

如果您的系统上的 find 和 xarg 版本不支持-print0-0切换(例如 AIX find 和 xargs),您可以使用这个看起来很糟糕的代码:

 find . -name "*foo*" | sed -e "s/'/\\\'/g" -e 's/"/\\"/g' -e 's/ /\\ /g' | xargs cp /your/dest

这里 sed 将负责转义 xargs 的空格和引号。

在 AIX 5.3 上测试

于 2015-03-27T14:49:21.203 回答
1

我玩了一点,开始考虑修改 xargs,并意识到对于我们在这里讨论的那种用例,在 Python 中进行简单的重新实现是一个更好的主意。

一方面,整个事情有大约 80 行代码意味着很容易弄清楚发生了什么,如果需要不同的行为,您可以在比获取时间更短的时间内将其破解为新脚本在 Stack Overflow 之类的地方回复。

请参阅https://github.com/johnallsup/jda-misc-scripts/blob/master/yargshttps://github.com/johnallsup/jda-misc-scripts/blob/master/zargs.py

使用 yargs 编写(并安装 Python 3),您可以键入:

find .|grep "FooBar"|yargs -l 203 cp --after ~/foo/bar

一次复制203个文件。(当然,这里的 203 只是一个占位符,使用像 203 这样的奇怪数字可以清楚地表明这个数字没有其他意义。)

如果您真的想要更快且不需要 Python 的东西,请将 zargs 和 yargs 作为原型并用 C++ 或 C 重写。

于 2015-08-05T19:54:41.323 回答
1

我围绕“xargs”创建了一个名为“xargsL”的小型便携式包装脚本,它解决了大部分问题。

与 xargs 不同,xargsL 每行接受一个路径名。路径名可以包含除(显然)换行符或 NUL 字节之外的任何字符。

文件列表中不允许或支持引用 - 您的文件名可能包含各种空格、反斜杠、反引号、shell 通配符等 - xargsL 会将它们作为文字字符处理,不会造成任何伤害。

作为一个额外的奖励功能,如果没有输入,xargsL 将不会运行该命令一次!

注意区别:

$ true | xargs echo no data
no data

$ true | xargsL echo no data # No output

给 xargsL 的任何参数都将传递给 xargs。

这是“xargsL”POSIX shell 脚本:

#! /bin/sh
# Line-based version of "xargs" (one pathname per line which may contain any
# amount of whitespace except for newlines) with the added bonus feature that
# it will not execute the command if the input file is empty.
#
# Version 2018.76.3
#
# Copyright (c) 2018 Guenther Brunthaler. All rights reserved.
#
# This script is free software.
# Distribution is permitted under the terms of the GPLv3.

set -e
trap 'test $? = 0 || echo "$0 failed!" >& 2' 0

if IFS= read -r first
then
        {
                printf '%s\n' "$first"
                cat
        } | sed 's/./\\&/g' | xargs ${1+"$@"}
fi

将脚本放入 $PATH 中的某个目录,不要忘记

$ chmod +x xargsL

那里的脚本使其可执行。

于 2018-03-17T17:47:48.223 回答
0

您可能需要 grep Foobar 目录,例如:

find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .
于 2013-06-26T16:02:22.980 回答
-1

如果您使用的是 Bash,则可以通过以下方式将stdout转换为行数组mapfile

find . | grep "FooBar" | (mapfile -t; cp "${MAPFILE[@]}" ~/foobar)

好处是:

  • 它是内置的,因此速度更快。
  • 一次执行所有文件名的命令,因此速度更快。
  • 您可以将其他参数附加到文件名。对于cp,您还可以:

    find . -name '*FooBar*' -exec cp -t ~/foobar -- {} +
    

    但是,有些命令没有这样的功能。

缺点:

  • 如果文件名太多,可能无法很好地扩展。(限制?我不知道,但我在 Debian 下测试了 10 MB 的列表文件,其中包含 10000 多个文件名,没有问题)

嗯...谁知道 Bash 在 OS X 上是否可用?

于 2014-05-21T02:50:59.663 回答