如何在xargs命令中使用“>”符号?

194

我想找到一个bash命令,可以让我在一个目录中grep每个文件,并将grep的输出写入单独的文件。我猜应该做类似于这样的事情:

ls -1 | xargs -I{} "grep ABC '{}' > '{}'.out"

据我所知,xargs不支持双引号。如果我移除了双引号,则该命令将把整个命令的输出重定向到一个名为'{}'.out的单个文件中,而不是一系列单独的文件。

有人知道如何使用xargs实现这一点吗?我只是用grep场景作为示例来说明我在使用xargs时遇到的问题,因此任何不使用xargs的解决方案对我来说都不太适用。

4个回答

234

不要犯这样的错误:

sh -c "grep ABC {} > {}.out"

这种方法在很多情况下都会出问题,包括一些奇怪的文件名,而且引用的方式也很难做到完全正确。为了避免代码注入漏洞,你的 {} 必须始终作为单独的参数传递给命令。你需要做的是:

xargs -I{} sh -c 'grep ABC "$1" > "$1.out"' -- {}

适用于xargsfind

顺便提一句,除非您不担心破坏数据并且进行了非常罕见和受控的一次性交互使用,否则永远不要使用xargs而不带-0选项。

此外,永远不要解析ls。改用通配符或者findhttp://mywiki.wooledge.org/ParsingLs

对于需要递归的所有内容,请使用find,对于其他内容,请使用简单的循环和通配符:

find /foo -exec sh -c 'grep "$1" > "$1.out"' -- {} \;

或者非递归:

for file in *; do grep "$file" > "$file.out"; done

注意引号的正确使用。


4
因为如果不使用 -0 参数,xargs 命令会在处理文件名时将其中的空格、引号和反斜杠等字符打散。你应该放弃使用 xargs 命令,并采用 Bash 循环语句来遍历每一行:while read line; do <command> "$REPLY"; done < file-with-lines,或者通过管道符将输出作为输入传递给 while 命令。 - lhunath
2
尽管我很感激对于这个特定用例的详细解释,但问题是关于重定向xargs输出的,这并不总是涉及解析ls或使用sh -c。这根本没有回答问题,但它是该问题的第一个谷歌搜索结果,只会增加混乱。 - pandasauce
1
@pandasauce请再次阅读问题的第一句话。这就是回答的内容。此外,答案中的第二个代码引用正是您要寻找的,并且与ls无关。如果有帮助的话,我稍微修改了答案以使其更清晰明了。 - lhunath
3
@Ihunath,你的答案对我很有用。但是,你能否提供关于xargs -I {} sh -c'grep ABC "$1">"$1.out"'--{}的详细解释或链接? 尤其是嵌套(双)引号的规则和末尾的“--”符号。谢谢。 - Scott Yang
2
@lhunath 实际上,--符号并没有什么特别之处。当使用命令执行sh时,提供的参数被分配给$0、$1等变量。当使用脚本执行sh(例如sh test.sh)时,脚本名称被分配给$0,第一个参数被分配给$1,以此类推。因此,将相同的命令保存在脚本中并执行,最终会导致所有参数都被移位。使用--(或_、turnip)作为丢弃变量,可以使您统一从偏移量1开始设置参数。该命令也可以不使用--而是使用$0来代替。 - Derek Greer
显示剩余8条评论

51

一种不使用xargs的解决方案如下:

find . -mindepth 1 -maxdepth 1 -type f -exec sh -c "grep ABC '{}' > '{}.out'" \;

使用 xargs 也可以做到同样的事情,如下所示:

ls -1 | xargs -I {} sh -c "grep ABC '{}' > '{}.out'"

编辑:在lhunath的评论后添加了单引号。


他说他想要使用xargs。我也发布了一个不需要它的解决方案,但是一旦我看到他需要xargs,就删除了它。 - Zifre
1
你说得对。我发表答案的原因是,有一个替代方案比没有更好。结果证明这让我找到了想要的答案(也就是 sh -c 技巧)。 - Stephan202

15

我假设你的例子只是一个示例,而且你可能需要 > 来完成其他事情。 GNU Parallel http://www.gnu.org/software/parallel/ 可能会帮到你。只要你的文件名不包含 \n,它就不需要额外的引号:

ls | parallel "grep ABC {} > {}.out"

如果您的文件名中含有 \n:

find . -print0 | parallel -0 "grep ABC {} > {}.out"

额外的好处是,您可以并行运行这些作业。

观看介绍视频以了解更多: http://pi.dk/1

安装只需10秒钟,将尝试进行完整安装;如果失败,则进行个人安装;如果仍然失败,则进行最小安装:

$ (wget -O - pi.dk/3 || lynx -source pi.dk/3 || curl pi.dk/3/ || \
   fetch -o - http://pi.dk/3 ) > install.sh
$ sha1sum install.sh | grep 883c667e01eed62f975ad28b6d50e22a
12345678 883c667e 01eed62f 975ad28b 6d50e22a
$ md5sum install.sh | grep cc21b4c943fd03e93ae1ae49e28573c0
cc21b4c9 43fd03e9 3ae1ae49 e28573c0
$ sha512sum install.sh | grep da012ec113b49a54e705f86d51e784ebced224fdf
79945d9d 250b42a4 2067bb00 99da012e c113b49a 54e705f8 6d51e784 ebced224
fdff3f52 ca588d64 e75f6033 61bd543f d631f592 2f87ceb2 ab034149 6df84a35
$ bash install.sh

如果您需要将其移动到没有安装GNU Parallel的服务器,请尝试使用parallel --embed


3

实际上,这里的大多数答案并不能适用于所有文件名(如果它们包含双引号和单引号),包括lhunath和Stephan202的答案。

此解决方案适用于带有单引号和双引号的文件名:

find . -mindepth 1 -print0 | xargs -0 -I{} sh -c 'grep ABC "$1" > "$1.out"' -- {}

这里有一个测试,包含单引号和双引号的文件名:

echo ABC > "I'm here.txt"

# lhunath solution (hangs waiting for input)

$ find . -exec sh -c 'grep "$1" > "$1.out"' -- {} \;

# Stephan202 solutions

$ find . -mindepth 1 -maxdepth 1 -type f -exec sh -c "grep ABC '{}' > '{}.out'" \;
grep: ./Im: No such file or directory
grep: here.txt > ./Im here.txt.out: No such file or directory

$ ls -1 | xargs -I {} sh -c "grep ABC '{}' > '{}.out'"
xargs: unterminated quote

# this solution
$ find . -mindepth 1 -print0 | xargs -0 -I{} sh -c 'grep ABC "$1" > "$1.out"' -- {}

$ ls -1
"I'm here.txt"
"I'm here.txt.out"

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