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


232

我正在尝试在目录下复制一堆文件,并且其中一些文件的名称中包含空格和单引号。当我尝试串起来findgrepxargs,我得到以下错误:

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

关于更可靠地使用xargs的任何建议?

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


2
对于GNU xargs错误消息,其文件名包含单引号会更有用:“ xargs:不匹配的单引号;默认情况下,除非您使用-0选项,否则引号对xargs来说是特殊的”。
史蒂夫·杰索普

3
GNU xargs也有--delimiter选项(-d)。尝试使用它\n作为定界符,这样可以防止xargs将带有空格的行分隔为多个单词/参数。
MattBianco '17

Answers:


199

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

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

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

注意:--传递给的标志cp可防止其处理-以options 开头的文件。


70
人们使用xargs的原因是,通常每次使用200个参数调用5次可执行文件要比每次使用一个参数调用1000次更快。
tzot

12
Chris Jester-Young的答案应该是那里的“好答案”……顺便说一句,如果文件名以“-”开头,则此解决方案不起作用。至少在cp后需要“-”。
Keltia

11
速度示例-超过829个文件,“ find -exec”方法花费了26秒,而“ find -print0 | xargs --null”方法工具花费了0.7秒。显着差异。
彼得·波特

7
@tzot最近的评论,但无论如何,xargs不需要使用它来解决您所描述的问题,并find已使用-exec +标点符号来支持它。
jlliagre 2013年

3
没有回答如何处理空间的问题
Ben Glasser

117

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

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


1
Leopard确实支持“ -Z”(它是GNU grep),当然find(1)和xargs(1)确实支持“ -0”。
Keltia

1
在OS X 10.9上grep -{z|Z}表示“表现为zgrep”(解压缩),而不是“在每个文件名后打印零字节”。使用grep --null以实现后者。
bassim 2014年

4
这有什么错find . -name 'FooBar' -print0 | xargs -0 ...
Quentin Pradet 2014年

1
@QuentinPradet显然,对于固定字符串,例如“ FooBar”,-name还是-path可以的。OP已指定使用grep,大概是因为他们想使用正则表达式过滤列表。
克里斯·杰斯特·杨

1
@ Hi-Angel这正是xargs -0 与结合使用的原因 find -print0。后者使用NUL终止符打印文件名,而前者以这种方式接收文件。为什么?Unix中的文件名可以包含换行符。但是它们不能包含NUL字符。
克里斯·杰斯特·杨

92

完成原始海报所需的最简单方法是将定界符从任何空格更改为仅行尾字符,如下所示:

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

4
这个答案很简单,有效并且直截了当:为xargs设置的默认定界符太宽了,需要缩小以适应OP想要做的事情。我是第一手知道的,因为今天除了在Cygwin中,我在做同样的事情时也遇到了类似的问题。如果我阅读了xargs命令的帮助,可能会避免一些麻烦,但是您的解决方案为我解决了这个问题。谢谢 !(是的,OP在MacOS上使用的是BSD xargs,我没有使用过,但是我希望xargs的“ -d”参数在所有版本中都存在)。
艾蒂安·德拉文纳特

7
不错的答案,但不适用于Mac。相反,我们可以通过管道将查找sed -e 's_\(.*\)_"\1"_g'周围的文件名力的报价
ishahak

10
这应该是公认的答案。问题是关于使用xargs
Mohammad Alhashash '16

2
我得到xargs: illegal option -- d
nehem

1
值得指出的是,在许多* nix系统上,文件名可以包含换行符。您不太可能在野外遇到这种情况,但是如果您在不受信任的输入上运行shell命令,则可能会感到担忧。
Soren Bjornstad '19

71

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

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

1
这对我不起作用。它试图将
cp〜

13
cp的-t标志是GNU扩展AFAIK,在OS X上不可用。但是,如果存在,它将按此答案所示工作。
metamatt 2012年

2
我正在使用Linux。感谢您使用“ -t”开关。那就是我所缺少的:-)
Vahid Pazirandeh '17

59

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

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

我曾经sed用同一行替换输入的每一行,但用双引号将其包围。在sed手册页中,“ ...替换中出现的&符(``&'')替换为与RE ...匹配的字符串 -在本例中.*为整行。

这样可以解决xargs: unterminated quote错误。


3
我在Windows上并使用gnuwin32,因此必须使用sed s/.*/\"&\"/它才能正常工作。
2012年

是的,但是大概不会用"in 处理文件名-除非sed也用引号引起来?
artfulrobot

使用sed是天才,现在是正确的解决方案,无需重写问题!
entonio

53

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

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

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


4
这是可行的,但-I暗含-L 1(因此是手册中所说的),这意味着cp命令每个文件= v运行一次。
artfulrobot

xargs -J%cp%<目标目录>在OSX上可能更​​有效。
Walker D

3
对不起,但这是错误的。首先,它产生了TO要避免的错误。您必须使用find ... -print0xargs -0工作arround xargs的“默认情况下引号是特殊的”。其次,通常在传递给xargs的命令中使用'{}'not {},以防止空格和特殊字符。
Andreas Spindler

3
抱歉,Andreas Spindler,我对xargs不太熟悉,经过一些试验后发现了这一行。对于大多数对此发表了评论并对此表示赞同的人来说,它似乎奏效。您介意对其产生什么样的错误进行更详细的介绍吗?另外,您介意发布您认为更正确的确切输入吗?谢谢。
the_minted

12

只是不要使用xargs。这是一个整洁的程序,但是find在遇到不重要的情况时效果不佳。

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

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

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

此解决方案:

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

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

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

  • 更有效(读取速度)比接受和最如果不是全部的答案。

还要注意,尽管在其他一些答复或评论中有说明,但引用{}是没有用的(除非您使用的是非常规fishshell)。



1
@PeterMortensen您可能忽略了结尾加号。find可以做xargs没有任何开销的事情。
jlliagre



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 2012年

1
|perl -lne 'print quotemeta'正是我一直在寻找的东西。这里的其他帖子对我没有帮助,因为不是find我需要使用它grep -rl来将PHP文件的数量大大减少为仅感染了恶意软件的文件。
Marcos

perl和quotemeta比print0 / -0更为通用-感谢使用流水线对文件进行流水线的通用解决方案
bmike 2015年

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”作为单个参数。


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作为文件名,而不是一个标志之后解释一切,所以先从文件---不要混淆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实用程序[自变量...] {} +与-exec相同,不同之处在于每次对实用程序的调用都将“ {}”替换为尽可能多的路径名。此行为类似于xargs(1 )。” 在BSD实施中。
康尼

2

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

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

我写了自己的专用版本的xargs(xargl)来处理名称中的空格问题(只有换行符分开-尽管'find ... -print0'和'xargs -0'的组合非常整洁,因为文件名不能包含ASCII NUL'\ 0'字符,我的xargl不够完善,值得发布-特别是因为GNU的功能至少与之相当。


2
GitHub或它没有发生
Corey Goldberg

@CoreyGoldberg:我想那没有发生。
乔纳森·莱夫勒

POSIX find不需要xargs一开始(这在11年前就已经成立)。
jlliagre

2

使用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/

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上测试


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

使其可执行的脚本。


1

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

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

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


1

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

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

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

我要访问的文件列表可能带有'-',但是没有换行符。我没有将输出文件与任何其他命令一起使用,因为在我开始通过xargs大规模删除它们之前,我想回顾一下发现的内容。


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重写。


0

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

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

1
每个手册页-i均已弃用,-I应改为使用。
Acumenus 2014年

-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上是否可用?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.