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

我试图复制一堆文件下面的目录和一些文件有空格和单引号在他们的名字。当我尝试将findgrepxargs串在一起时,我得到以下错误:

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

对于更健壮地使用xargs有什么建议吗?

这是在Mac  OS  X 10.5.3 (Leopard)上使用BSD xargs

263714 次浏览

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

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

查看一下在xargs中使用——null命令行选项和find中的-print0选项。

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

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

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

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

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

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

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

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

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

我发现下面的语法很适合我。

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字节的文件。

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

bill_starr的Perl版本不能很好地用于嵌入的换行符(只处理空格)。对于那些没有GNU工具的Solaris,一个更完整的版本可能是(使用sed)…

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

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

我在Solaris上使用了稍微修改过的比尔·斯达的回答:

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

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

我要去的文件列表可能有'-',但没有换行符。我没有使用输出文件与任何其他命令,因为我想回顾什么是发现之前,我只是开始大规模删除他们通过xargs。

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

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

我也遇到了同样的问题。以下是我的解决方法:

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

我使用sed将每一行输入替换为同一行,但用双引号括起来。在sed手册页中,“...替换中出现的&号('' & ")将被匹配RE…”——在本例中是.*,即整行。

这解决了xargs: unterminated quote错误。

你可能需要像这样grep Foobar目录:

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

只是不要使用xargs。这是一个简洁的程序,但当面对非平凡的情况时,它不适合find

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

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

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

这个解决方案:

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

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

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

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

还要注意,尽管在其他一些回复或注释中声明了什么,引用{}是没有用的(除非你使用的是奇异的fishshell)。

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

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

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

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

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

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

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

如果你正在使用Bash,你可以通过mapfilestdout转换为一个行数组:

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

好处是:

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

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

    但是,有些命令没有这个特性

的缺点:

  • 如果文件名太多,可能伸缩性不好。(极限?我不知道,但我已经在Debian下测试了10 MB的列表文件,其中包括10000多个文件名,没有问题)

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

如果你的系统上的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上测试

我尝试了一下,开始考虑修改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重写。

对我来说,我想做一些不一样的事情。我想复制我的txt文件到我的tmp文件夹。txt文件包含空格和撇号。这在我的Mac上很管用。

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

最简单的方法来做原来的海报想要的是改变分隔符从任何空白到只是行结束字符,像这样:

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

我围绕“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

那里的脚本使它可执行。

框架挑战-你正在询问如何使用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将后面的所有内容解释为文件名,而不是标志,因此以---开头的文件不会混淆cp;你仍然需要它,因为-/--字符是由cp解释的,而任何其他特殊字符是由shell解释的。

  • 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调用的需要,这样您就不需要担心文件名中任何讨厌的字符。