如何使用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个回答

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个文件。
Perl的一行代码在“find”和“xargs”之间转义/引用每个空格,以便“xargs”将带有嵌入式空格的任何文件名作为单个参数传递给“ls”。

4
框架挑战——你正在询问如何使用xargs。答案是:你不需要使用xargs,因为你不需要它。 < p >(此处有html标签)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调用,因此您不需要担心文件名中的任何糟糕字符。


太神奇了,我完全不知道!!! "-exec utility [argument ...] {} + 与-exec相同,只是“{}”在每次调用实用程序时被尽可能多的路径名替换。 在BSD实现中,此行为类似于xargs(1)"。 - conny

3

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

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

2

对我而言,我尝试着做一些不同的事情。我想将我的 .txt 文件复制到 tmp 文件夹中。这些 .txt 文件名包含空格和撇号字符。在我的 Mac 上可以工作。

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

2
请注意,其他回答中讨论的大多数选项在不使用GNU实用程序(例如Solaris,AIX,HP-UX等)的平台上不是标准选项。请参阅POSIX规范以获取“标准”xargs行为。
我还发现xargs的行为很烦人,即使没有输入,它也会至少运行一次命令。
我编写了自己的xargs版本(xargl)来解决名称中空格的问题(只有换行符分隔 - 虽然“find ... -print0”和“xargs -0”组合非常好,因为文件名不能包含ASCII NUL '\0'字符)。我的xargl并不像它需要发布的那样完整 - 特别是GNU具有至少同样好的功能。

2
GitHub或者没有发生过。 - Corey Goldberg
@CoreyGoldberg:我猜那时候没发生。 - Jonathan Leffler
POSIX的“find”一开始就不需要“xargs”(而且这在11年前就已经是事实了)。 - jlliagre

1
如果您的系统上的find和xargs版本不支持-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上测试通过。


1

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

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

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

我正在处理的文件列表可能包含“-”,但不包含换行符。我还没有将输出文件与其他命令一起使用,因为我想在通过xargs大规模删除它们之前先查看找到了什么。


1

Bill Starr的Perl版本无法很好地处理嵌入式换行符(只能处理空格)。对于那些没有GNU工具的操作系统,比如Solaris,一个更完整的版本可能是(使用sed)...

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

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


1
我创建了一个名为“xargsL”的小型便携式包装脚本,它围绕“xargs”解决了大部分问题。
与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

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中重新编写。


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