如何使用xargs复制名称中包含空格和引号的文件?

时间:2008-09-27 07:10:48

标签: linux macos unix command-line xargs

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

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

有关更强大的xargs使用的任何建议吗?

这是Mac OS X 10.5.3(Leopard)与BSD xargs

22 个答案:

答案 0 :(得分:191)

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

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

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

注意:传递给--的{​​{1}}标记会阻止它处理以cp开头的文件作为选项。

答案 1 :(得分:114)

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

我不知道grep是否支持--null,也不知道xargs是否支持-0,而是使用GNU,这一切都很好。

答案 2 :(得分:76)

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

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

答案 3 :(得分:69)

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

find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar

答案 4 :(得分:55)

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

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

我使用sed用相同的行替换每行输入,但用双引号括起来。从sed手册页,“ ...替换中出现的&符号(``&'')被替换为匹配RE的字符串... ” - in这种情况,.*,整行。

这解决了xargs: unterminated quote错误。

答案 5 :(得分:50)

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

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

我还测试了您发布的确切语法。这也适用于10.7.5。

答案 6 :(得分:11)

请勿使用xargs。这是一个很好的程序,但在面对非平凡的案例时,find并不顺利。

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

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

请注意结尾+,而不是更常见的;

此解决方案:

  • 正确处理包含空格,换行符或任何异域字符的文件和目录。

  • 可以在任何Unix和Linux系统上运行,甚至是那些不提供GNU工具包的系统。

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

  • 效率更高(读取更快)比接受的和大多数(如果不是全部)其他答案都要好。

另请注意,尽管引用{}的其他一些回复或评论中所述内容毫无用处(除非您使用的是异域fish shell)。

答案 7 :(得分:8)

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

答案 8 :(得分:8)

对于那些依赖命令的人,除了查找,例如ls

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

答案 9 :(得分:5)

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

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

答案 10 :(得分:5)

我发现以下语法对我有用。

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

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

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

答案 11 :(得分:2)

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

$ find . -type f -name '*.txt' | sed 's/'"'"'/\'"'"'/g' | sed 's/.*/"&"/'  | xargs -I{} cp -v {} ./tmp/

答案 12 :(得分:2)

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

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

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

答案 13 :(得分:1)

我在Solaris上稍微修改了Bill Star's answer

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

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

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

答案 14 :(得分:1)

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

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

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

答案 15 :(得分:1)

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

while read line ; do cp "$line" ~/bar ; done < <(find . | grep foo)

答案 16 :(得分: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上测试

答案 17 :(得分: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重写。

答案 18 :(得分:1)

我创建了一个名为&#34; xargsL&#34;的小型便携包装脚本。周围&#34; xargs&#34;它解决了大部分问题。

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

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

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

注意区别:

$ true | xargs echo no data
no data

$ true | xargsL echo no data # No output

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

这是&#34; xargsL&#34; 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

那里的脚本使其可执行。

答案 19 :(得分:0)

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

find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .

答案 20 :(得分:0)

框架挑战-您正在询问如何使用xargs。答案是:您不需要xargs,因为您不需要它。

comment by 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将所有内容解释为文件名而不是标志,因此以---开头的文件不会混淆{ {1}};您仍然需要这样做,因为cp / -字符由--解释,而其他任何特殊字符由shell解释。

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

man find

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

答案 21 :(得分:-1)

如果您使用的是Bash,则可以通过mapfile stdout 转换为一系列行:

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

好处是:

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

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

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

缺点:

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

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