简体   繁体   English

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

[英]How can I use xargs to copy files that have spaces and quotes in their names?

I'm trying to copy a bunch of files below a directory and a number of the files have spaces and single-quotes in their names. 我正在尝试将一堆文件复制到目录下,并且许多文件的名称中都有空格和单引号。 When I try to string together find and grep with xargs , I get the following error: 当我尝试使用xargs串起来findgrep时,我收到以下错误:

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

Any suggestions for a more robust usage of xargs? 有关更强大的xargs使用的任何建议?

This is on Mac OS X 10.5.3 (Leopard) with BSD xargs . 这是在带有BSD xargs Mac OS X 10.5.3 (Leopard)上。

You can combine all of that into a single find command: 您可以将所有这些组合到一个find命令中:

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

This will handle filenames and directories with spaces in them. 这将处理其中包含空格的文件名和目录。 You can use -name to get case-sensitive results. 您可以使用-name来获取区分大小写的结果。

Note: The -- flag passed to cp prevents it from processing files starting with - as options. 注意:传递给cp--标志阻止它处理以-作为选项开头的文件。

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

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

做原始海报想要的最简单方法是将分隔符从任何空格更改为行尾字符,如下所示:

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

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

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

I ran into the same problem. 我遇到了同样的问题。 Here's how I solved it: 这是我解决它的方式:

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

I used sed to substitute each line of input with the same line, but surrounded by double quotes. 我使用sed用相同的行替换每行输入,但用双引号括起来。 From the sed man page, " ...An ampersand (``&'') appearing in the replacement is replaced by the string matching the RE... " -- in this case, .* , the entire line. sed手册页中,“ ......替换中出现的&符号(``&'')被替换为匹配RE的字符串... ” - 在这种情况下, .* ,整行。

This solves the xargs: unterminated quote error. 这解决了xargs: unterminated quote错误。

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

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

I also tested the exact syntax you posted. 我还测试了你发布的确切语法。 That also worked fine on 10.7.5. 这也适用于10.7.5。

Just don't use xargs . 只是不要使用xargs It is a neat program but it doesn't go well with find when faced with non trivial cases. 这是一个很好的程序,但是当遇到非平凡的情况时,它并不适合find

Here is a portable (POSIX) solution, ie one that doesn't require find , xargs or cp GNU specific extensions: 这是一个便携式(POSIX)解决方案,即不需要findxargscp GNU特定扩展的解决方案:

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

Note the ending + instead of the more usual ; 注意结尾+而不是更常见; .

This solution: 此解决方案:

  • correctly handles files and directories with embedded spaces, newlines or whatever exotic characters. 正确处理带有嵌入空格,换行符或任何异域字符的文件和目录。

  • works on any Unix and Linux system, even those not providing the GNU toolkit. 适用于任何Unix和Linux系统,甚至是那些不提供GNU工具包的系统。

  • doesn't use xargs which is a nice and useful program, but requires too much tweaking and non standard features to properly handle find output. 不使用xargs这是一个很好的和有用的程序,但需要太多的调整和非标准功能来正确处理find输出。

  • is also more efficient (read faster ) than the accepted and most if not all of the other answers. 也比接受的和大多数(如果不是全部)其他答案更有效 (读取更快 )。

Note also that despite what is stated in some other replies or comments quoting {} is useless (unless you are using the exotic fish shell). 还需要注意的是,尽管什么在其他一些回复或评论引述说{}是无用的(除非您使用的是异国情调的fish贝)。

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

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

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

I have found that the following syntax works well for me. 我发现以下语法对我很有用。

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

In this example, I am looking for the largest 200 files over 1,000,000 bytes in the filesystem mounted at "/usr/pcapps". 在这个例子中,我正在寻找安装在“/ usr / pcapps”的文件系统中超过1,000,000字节的最大200个文件。

The Perl line-liner between "find" and "xargs" escapes/quotes each blank so "xargs" passes any filename with embedded blanks to "ls" as a single argument. “find”和“xargs”之间的Perl换行符转义/引用每个空格,因此“xargs”将任何带有嵌入空格的文件名作为单个参数传递给“ls”。

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

I believe that this will work reliably for any character except line-feed (and I suspect that if you've got line-feeds in your filenames, then you've got worse problems than this). 我相信这对于除换行之外的任何角色都能可靠地工作(我怀疑如果你的文件名中有换行符,那么你遇到的问题比这更糟)。 It doesn't require GNU findutils, just Perl, so it should work pretty-much anywhere. 它不需要GNU findutils,只需要Perl,因此它应该可以在任何地方工作。

Be aware that most of the options discussed in other answers are not standard on platforms that do not use the GNU utilities (Solaris, AIX, HP-UX, for instance). 请注意,其他答案中讨论的大多数选项在不使用GNU实用程序(例如,Solaris,AIX,HP-UX)的平台上不是标准选项。 See the POSIX specification for 'standard' xargs behaviour. 请参阅POSIX规范以了解“标准”xargs行为。

I also find the behaviour of xargs whereby it runs the command at least once, even with no input, to be a nuisance. 我还发现xargs的行为,即使没有输入,它至少运行一次命令,这是一个麻烦。

I wrote my own private version of xargs (xargl) to deal with the problems of spaces in names (only newlines separate - though the 'find ... -print0' and 'xargs -0' combination is pretty neat given that file names cannot contain ASCII NUL '\\0' characters. My xargl isn't as complete as it would need to be to be worth publishing - especially since GNU has facilities that are at least as good. 我编写了自己的私有版本的xargs(xargl)来处理名称中的空格问题(只有换行符分开 - 尽管'find ... -print0'和'xargs -0'组合非常简洁,因为文件名不能包含ASCII NUL'\\ 0'字符。我的xargl并不像它需要值得发布那样完整 - 特别是因为GNU的设施至少同样好。

With Bash (not POSIX) you can use process substitution to get the current line inside a variable. 使用Bash(不是POSIX),您可以使用进程替换来获取变量中的当前行。 This enables you to use quotes to escape special characters: 这使您可以使用引号来转义特殊字符:

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

For me, I was trying to do something a little different. 对我来说,我试图做一些不同的事情。 I wanted to copy my .txt files into my tmp folder. 我想将我的.txt文件复制到我的tmp文件夹中。 The .txt filenames contain spaces and apostrophe characters. .txt文件名包含空格和撇号字符。 This worked on my Mac. 这适用于我的Mac。

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

bill_starr's Perl version won't work well for embedded newlines (only copes with spaces). bill_starr的Perl版本不适用于嵌入式换行符(仅处理空格)。 For those on eg Solaris where you don't have the GNU tools, a more complete version might be (using sed)... 对于那些没有GNU工具的Solaris,可能会有更完整的版本(使用sed)......

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

adjust the find and grep arguments or other commands as you require, but the sed will fix your embedded newlines/spaces/tabs. 根据需要调整find和grep参数或其他命令,但sed将修复嵌入的换行符/空格/制表符。

If find and xarg versions on your system doesn't support -print0 and -0 switches (for example AIX find and xargs) you can use this terribly looking code: 如果系统上的find和xarg版本不支持-print0-0开关(例如AIX find和xargs),则可以使用这个非常外观的代码:

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

Here sed will take care of escaping the spaces and quotes for xargs. 这里sed将负责转义xargs的空格和引号。

Tested on AIX 5.3 在AIX 5.3上测试过

I played with this a little, started contemplating modifying xargs, and realised that for the kind of use case we're talking about here, a simple reimplementation in Python is a better idea. 我稍微玩了一下,开始考虑修改xargs,并意识到对于我们在这里讨论的那种用例,Python中的简单重新实现是一个更好的主意。

For one thing, having ~80 lines of code for the whole thing means it is easy to figure out what is going on, and if different behaviour is required, you can just hack it into a new script in less time than it takes to get a reply on somewhere like Stack Overflow. 一方面,整个事情有大约80行代码意味着很容易弄清楚发生了什么,如果需要不同的行为,你可以在更短的时间内将其破解成新的脚本而不是获取像Stack Overflow这样的回复。

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

With yargs as written (and Python 3 installed) you can type: 使用yargs编写(并安装了Python 3),您可以键入:

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

to do the copying 203 files at a time. 一次复制203个文件。 (Here 203 is just a placeholder, of course, and using a strange number like 203 makes it clear that this number has no other significance.) (当然,203这里只是一个占位符,使用像203这样的奇怪数字可以清楚地表明这个数字没有其他意义。)

If you really want something faster and without the need for Python, take zargs and yargs as prototypes and rewrite in C++ or C. 如果你真的想要更快的东西并且不需要Python,那么将zargs和yargs作为原型并在C ++或C中重写。

I used Bill Star's answer slightly modified on Solaris: 我在Solaris上稍微修改了Bill Star的答案

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

This will put quotes around each line. 这将在每一行周围加上引号。 I didn't use the '-l' option although it probably would help. 我没有使用'-l'选项,虽然它可能会有所帮助。

The file list I was going though might have '-', but not newlines. 我去的文件列表可能有' - ',但不是新行。 I haven't used the output file with any other commands as I want to review what was found before I just start massively deleting them via xargs. 我没有将输出文件与任何其他命令一起使用,因为我想在我开始通过xargs大规模删除它们之前查看找到的内容。

I created a small portable wrapper script called "xargsL" around "xargs" which addresses most of the problems. 我在“xargs”周围创建了一个名为“xargsL”的小型便携包装脚本,它解决了大多数问题。

Contrary to xargs, xargsL accepts one pathname per line. 与xargs相反,xargsL每行接受一个路径名。 The pathnames may contain any character except (obviously) newline or NUL bytes. 路径名可以包含除(显然)换行符或NUL字节之外的任何字符。

No quoting is allowed or supported in the file list - your file names may contain all sorts of whitespace, backslashes, backticks, shell wildcard characters and the like - xargsL will process them as literal characters, no harm done. 文件列表中不允许或支持引用 - 您的文件名可能包含各种空格,反斜杠,反引号,shell通配符等 - xargsL会将它们作为文字字符处理,不会造成任何损害。

As an added bonus feature, xargsL will not run the command once if there is no input! 作为额外的奖励功能,如果没有输入,xargsL将不会运行一次命令!

Note the difference: 注意区别:

$ true | xargs echo no data
no data

$ true | xargsL echo no data # No output

Any arguments given to xargsL will be passed through to xargs. 给予xargsL的任何参数都将传递给xargs。

Here is the "xargsL" POSIX shell script: 这是“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 

Put the script into some directory in your $PATH and don't forget to 将脚本放入$ PATH中的某个目录中,不要忘记

$ chmod +x xargsL

the script there to make it executable. 那里的脚本使其可执行。

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

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

Frame challenge — you're asking how to use xargs. 帧挑战 - 你问的是如何使用xargs。 The answer is: you don't use xargs, because you don't need it. 答案是:你不使用xargs,因为你不需要它。

The comment by user80168 describes a way to do this directly with cp, without calling cp for every file: user80168注释描述了一种直接用cp执行此操作的方法,而不需要为每个文件调用cp:

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

This works because: 这是因为:

  • the cp -t flag allows to give the target directory near the beginning of cp , rather than near the end. cp -t标志允许将目标目录放在cp的开头附近,而不是接近结尾。 From man cp : 来自man cp
  -t, --target-directory=DIRECTORY copy all SOURCE arguments into DIRECTORY 
  • The -- flag tells cp to interpret everything after as a filename, not a flag, so files starting with - or -- do not confuse cp ; --标志告诉cp来解释一切作为文件名,而不是一个标志,所以开头的文件后---不要混淆cp ; you still need this because the - / -- characters are interpreted by cp , whereas any other special characters are interpreted by the shell. 你仍然需要这个,因为- / --字符由cp解释,而任何其他特殊字符由shell解释。

  • The find -exec command {} + variant essentially does the same as xargs. find -exec command {} + variant基本上与xargs相同。 From man find : 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. 

By using this in find directly, this avoids the need of a pipe or a shell invocation, such that you don't need to worry about any nasty characters in filenames. 通过在find中直接使用它,这可以避免需要管道或shell调用,这样您就不必担心文件名中的任何讨厌字符。

If you are using Bash, you can convert stdout to an array of lines by mapfile : 如果您使用的是Bash,则可以通过mapfilestdout转换为行数组:

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

The benefits are: 好处是:

  • It's built-in, so it's faster. 它是内置的,所以速度更快。
  • Execute the command with all file names in one time, so it's faster. 一次执行带有所有文件名的命令,因此速度更快。
  • You can append other arguments to the file names. 您可以将其他参数附加到文件名。 For cp , you can also: 对于cp ,您还可以:

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

    however, some commands don't have such feature. 但是,有些命令没有这样的功能。

The disadvantages: 缺点:

  • Maybe not scale well if there are too many file names. 如果文件名太多,可能无法很好地扩展。 (The limit? I don't know, but I had tested with 10 MB list file which includes 10000+ file names with no problem, under Debian) (限制?我不知道,但我已经测试了10 MB列表文件,其中包含10000多个文件名,没有问题,在Debian下)

Well... who knows if Bash is available on OS X? 那么......谁知道OS X上是否有Bash可用?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM