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

发布于 2024-07-06 10:41:57 字数 411 浏览 6 评论 0原文

我正在尝试复制目录下的一堆文件,其中许多文件的名称中包含空格和单引号。 当我尝试将 findgrepxargs 串在一起时,出现以下错误:

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

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

这是在 Mac OS X 10.5.3 (Leopard) 和 BSD xargs< /代码>。

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:

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

Any suggestions for a more robust usage of xargs?

This is on Mac OS X 10.5.3 (Leopard) with BSD xargs.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(22

草莓酥 2024-07-13 10:41:58

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

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

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

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

You can combine all of that into a single find command:

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.

Note: The -- flag passed to cp prevents it from processing files starting with - as options.

如何视而不见 2024-07-13 10:41:58

<代码>查找 . -打印0 | grep --null 'FooBar' | grep --null 'FooBar' | grep --null 'FooBar' | grep --null 'FooBar' xargs -0 ...

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

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

I don't know about whether grep supports --null, nor whether xargs supports -0, on Leopard, but on GNU it's all good.

浪推晚风 2024-07-13 10:41:58

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

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

The easiest way to do what the original poster wants is to change the delimiter from any whitespace to just the end-of-line character like this:

find whatever ... | xargs -d "\n" cp -t /var/tmp
梦里人 2024-07-13 10:41:58

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

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

This is more efficient as it does not run "cp" multiple times:

find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar
娇俏 2024-07-13 10:41:58

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

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

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

这解决了xargs:未终止的引号错误。

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. 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.

This solves the xargs: unterminated quote error.

開玄 2024-07-13 10:41:58

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

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

我还测试了您发布的确切语法。 这在 10.7.5 上也运行良好。

This method works on 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.

浮光之海 2024-07-13 10:41:58

只是不要使用xargs。 这是一个简洁的程序,但在面对重要情况时,它与 find 的配合不太好。

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

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

请注意结尾 < code>+ 而不是更常见的 ;

此解决方案:

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

  • 适用于任何 Unix 和 Linux 系统,甚至不提供 GNU 工具包的系统。

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

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

另请注意,尽管在其他一些回复或评论中有所说明,但引用 {} 是没有用的(除非您使用的是异国情调的 fishshell)。

Just don't use xargs. It is a neat program but it doesn't go well with find when faced with non trivial cases.

Here is a portable (POSIX) solution, i.e. one that doesn't require find, xargs or cp GNU specific extensions:

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.

  • 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.

  • 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 fishshell).

八巷 2024-07-13 10:41:58

对于那些依赖除 find 之外的命令的人,例如 ls

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

For those who relies on commands, other than find, eg ls:

find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar
假扮的天使 2024-07-13 10:41:58

考虑将 xargs 的 --null 命令行选项与 find 中的 -print0 选项一起使用。

Look into using the --null commandline option for xargs with the -print0 option in find.

橘和柠 2024-07-13 10:41:58
find | perl -lne 'print quotemeta' | xargs ls -d

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

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.

累赘 2024-07-13 10:41:58

我发现以下语法对我来说效果很好。

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”。

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".

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.

浅忆流年 2024-07-13 10:41:58

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

user80168 的评论描述了一种直接使用 cp 执行此操作的方法,无需为每个文件调用 cp:

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

这有效的原因是:

  • cp -t 标志允许在 cp 开头附近而不是末尾附近给出目标目录。 来自 man cp
 -t, --target-directory=目录 
           将所有 SOURCE 参数复制到 DIRECTORY 中 
  
  • -- 标志告诉 cp 将后面的所有内容解释为文件名,而不是标志,因此以 -开头的文件>-- 不要混淆cp; 您仍然需要这个,因为 -/-- 字符由 cp 解释,而任何其他特殊字符由 shell 解释。

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

<前><代码> -exec 命令 {} +
-exec 操作的此变体在以下位置运行指定的命令
选定的文件,但命令行是通过附加构建的
每个选定的文件名在末尾; 调用总数
匹配的文件。 命令行的构建方式大致相同
xargs 构建其命令行。 只有一个“{}”实例
在命令中允许,并且(当调用 find 时
来自 shell)应该用引号引起来(例如“{}”)以保护
它来自 shell 的解释。 该命令执行于
起始目录。 如果任何调用返回非零
value 作为退出状态,然后 find 返回非零退出状态。
如果 find 遇到错误,有时会立即导致
ate exit,因此某些挂起的命令可能根本无法运行。 这
-exec 的变体始终返回 true。

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

Frame challenge — you're asking how to use xargs. The answer is: you don't use xargs, because you don't need it.

The comment by user80168 describes a way to do this directly with cp, without calling cp for every file:

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. From 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; you still need this because the -/-- characters are interpreted by cp, whereas any other special characters are interpreted by the shell.

  • The find -exec command {} + variant essentially does the same as xargs. From 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.

零度℉ 2024-07-13 10:41:58

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

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

With Bash (not POSIX) you can use process substitution to get the current line inside a variable. This enables you to use quotes to escape special characters:

while read line ; do cp "$line" ~/bar ; done < <(find . | grep foo)
感性 2024-07-13 10:41:58

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

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

我编写了自己的 xargs (xargl) 私有版本来处理名称中的空格问题(仅换行符分隔 - 尽管“find ... -print0”和“xargs -0”组合非常简洁,因为文件名不能包含 ASCII NUL '\0' 字符 我的 xargl 并不完整,值得发布 - 特别是因为 GNU 拥有至少同样好的设施。

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). See the POSIX specification for 'standard' xargs behaviour.

I also find the behaviour of xargs whereby it runs the command at least once, even with no input, to be a nuisance.

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.

倾`听者〃 2024-07-13 10:41:58

对我来说,我试图做一些不同的事情。 我想将 .txt 文件复制到 tmp 文件夹中。 .txt 文件名包含空格和撇号字符。 这在我的 Mac 上有效。

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

For me, I was trying to do something a little different. I wanted to copy my .txt files into my tmp folder. The .txt filenames contain spaces and apostrophe characters. This worked on my Mac.

$ find . -type f -name '*.txt' | sed 's/'"'"'/\'"'"'/g' | sed 's/.*/"&"/'  | xargs -I{} cp -v {} ./tmp/
心凉 2024-07-13 10:41:58

如果您系统上的 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将负责转义 xargs 的空格和引号。

在 AIX 5.3 上测试

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 . -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.

Tested on AIX 5.3

提笔落墨 2024-07-13 10:41:58

我围绕“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
# 基于行的“xargs”版本(每行一个路径名,可以包含任何
# 除换行符之外的空白量)以及额外的奖励功能
# 如果输入文件为空,则不会执行该命令。
#
# 版本2018.76.3
#
# 版权所有 (c) 2018 Guenther Brunthaler。 版权所有。
#
# 该脚本是免费软件。
# 根据 GPLv3 条款允许分发。

设置-e
陷阱'测试$? = 0 || echo "$0 失败!" >& 2'0

如果 IFS= 首先读取 -r
然后
{
printf '%s\n' "$first"

} | sed 's/./\\&/g' | sed 's/./\\&/g' | xargs ${1+"$@"}

将脚本放入 $PATH 中的某个目录中,并且不要忘记

$ chmod +x xargsL

脚本以使其可执行。

I created a small portable wrapper script called "xargsL" around "xargs" which addresses most of the problems.

Contrary to xargs, xargsL accepts one pathname per line. The pathnames may contain any character except (obviously) newline or NUL bytes.

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.

As an added bonus feature, xargsL will not run the command once if there is no input!

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.

Here is the "xargsL" POSIX shell script:

#! /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

$ chmod +x xargsL

the script there to make it executable.

A君 2024-07-13 10:41:58

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

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

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

bill_starr's Perl version won't work well for embedded newlines (only copes with spaces). For those on e.g. Solaris where you don't have the GNU tools, a more complete version might be (using 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.

鲜血染红嫁衣 2024-07-13 10:41:58

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

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

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

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

I used Bill Star's answer slightly modified on Solaris:

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.

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.

木有鱼丸 2024-07-13 10:41:58

我玩了一下,开始考虑修改 xargs,并意识到对于我们在这里讨论的用例,用 Python 进行简单的重新实现是一个更好的主意。

一方面,整个事情有大约 80 行代码意味着很容易弄清楚发生了什么,如果需要不同的行为,您可以将其破解为新脚本,所需时间比获得所需的时间要短在 StackOverflow 等地方的回复。

请参阅 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 重写。

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.

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.

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

With yargs as written (and Python 3 installed) you can type:

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

to do the copying 203 files at a time. (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.)

If you really want something faster and without the need for Python, take zargs and yargs as prototypes and rewrite in C++ or C.

峩卟喜欢 2024-07-13 10:41:58

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

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

You might need to grep Foobar directory like:

find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .
死开点丶别碍眼 2024-07-13 10:41:58

如果您使用的是 Bash,则可以通过 mapfilestdout 转换为行数组:

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

优点是:

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

    <前><代码>查找 . -name '*FooBar*' -exec cp -t ~/foobar -- {} +

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

缺点:

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

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

If you are using Bash, you can convert stdout to an array of lines by mapfile:

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:

    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)

Well... who knows if Bash is available on OS X?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文