每行管道输入执行一次命令?


161

我想为的每次匹配都运行一次Java命令ls | grep pattern -。在这种情况下,我想我可以做,find pattern -exec java MyProg '{}' \;但是我对一般情况感到很好奇-是否有一种简单的方式说“每行标准输入运行一次命令”?(以鱼或b)。

Answers:


91

就是xargs那样

... | xargs command

25
不完全的。 printf "foo bar\nbaz bat" | xargs echo whee将屈服whee foo bar baz bat。也许添加-L-n选项?
詹德,

3
@Jander这个问题相当笼统,所以我给出了常规工具。没错,您必须根据具体情况使用选项调整其行为。
基思

4
... | tr'\ n''\ 0'| xargs -0
vrdhn

7
例如“为问题提供正确答案的特定情况”。:)
mattdm 2011年

7
如果您想了解使用xargs执行此操作的正确方法,请参见下面的答案。
Michael Goldshteyn

166

接受的答案具有正确的想法,但关键是要通过xargs-n1开关,这意味着“每输出的线一旦执行命令:”

cat file... | xargs -n1 command

或者,对于单个输入文件,您可以cat完全避免使用管道,而只需执行以下操作:

<file xargs -n1 command

1
同样感兴趣的是能力xargs,以运行,如果stdin是空的: --no-run-if-empty -r:如果标准输入不包含任何非空白,不运行命令。通常,即使没有输入,命令也会运行一次。此选项是GNU扩展。
罗南·朱谢

4
您如何访问内部线路command
BT

这是xargs的正确用法。如果不使用-n1,则它仅适用于将参数列表视为多次调用的命令,而并非全部。
masterxilo

3
printf“ foo bar \ nbaz bat” | xargs -n1 echo whee按单词而不是按行拆分
Gismo Ranas,

112

在Bash或任何其他Bourne风格的shell(ash,ksh,zsh等)中:

while read -r line; do command "$line"; done

read -r从标准输入中读取一行(read-r解释反斜线,则不需要这样做)。因此,您可以执行以下任一操作:

$ command | while read -r line; do command "$line"; done  

$ while read -r line; do command "$line"; done <file

6
当我尝试tail -f syslog | grep -e something -e somethingelse| while read line; do echo $line; done时不起作用。它使用通过管道传递到while循环中的文件,仅使用tail -f,使用just grep,但不适用于两个管道。给出grep--line-buffered选项使它的工作

这工作也当每条线都需要被发送到标准输入:command | while read -r line; do echo "$line" | command ; done
小室

21

我同意Keith的观点,xargs是最通用的工具。

我通常使用3个步骤。

  • 做一些基本的事情,直到您想使用一些东西为止
  • 用awk准备该行,以获取正确的语法
  • 然后让xargs也许在bash的帮助下执行它。

有更小更快的方法,但是这种方法几乎总是可行的。

一个简单的例子:

ls | 
grep xls | 
awk '{print "MyJavaProg --arg1 42 --arg2 "$1"\0"}' | 
xargs -0 bash -c

前两行选择了一些要使用的文件,然后awk用一个要执行的命令和一些参数准备了一个不错的字符串,$ 1是管道的第一列输入。最后,我确保xargs将此字符串发送给刚执行的bash。

这有点矫over过正,但是由于它非常灵活,因此在很多地方对我都有帮助。


6
注意,xargs -0使用空字节作为记录分隔符,因此您的awk打印语句应为printf("MyJavaProg --args \"%s\"\0",$1)
glenn jackman 2011年

@glenn:缺少空字符,将更新答案
Johan

@Johan没什么大不了的,但是如果您正在使用awk,可以让它进行模式匹配并跳过grep 例如,ls | awk '/xls/ {print...
Eric Renouf

15

GNU Parallel专为此类任务而设计。最简单的用法是:

cat stuff | grep pattern | parallel java MyProg

观看介绍性视频以了解更多信息:http : //www.youtube.com/watch?v=OpaiGYxkSuQ


1
cat这里没有真正的需要,因为grep可以直接读取文件
Eric Renouf


1
感谢您提供的链接,我不一定同意它更容易阅读,但是很高兴知道它被考虑了。我现在只是稍微怀疑一下,该链接实际上并没有在这里应用,因为替代方法不是真的,< stuff grep pattern但是根本grep pattern stuff不需要重定向或cat。不过,这并不会从根本上改变您的论点,如果您认为始终在以开头的管道中使用东西是cat
显而易见的

8

另外,while read在鱼壳中循环(考虑到您使用的是标签,我假设您需要鱼壳)。

command | while read line
    command $line
end

需要注意的几点。

  • read-r为了使最常见的用例变得容易,不接受参数,也不解释反斜杠。
  • 您不需要引用$line,就像bash一样,fish不会用空格分隔变量。
  • command本身就是语法错误(以捕获占位符参数的这种用法)。用real命令替换它。

并不while需要与配对dodone代替end
AFF

@aff这是专门关于鱼壳的,它具有不同的语法。
Konrad Borowski

啊,就是鱼的意思。
AFF

6

如果需要控制将输入参数确切地插入命令行的位置,或者需要重复几次,则需要使用xargs -I{}

示例#1

在其中创建一个空文件夹结构,another_folder以镜像当前目录中的子文件夹:

    ls -1d ./*/ | xargs -I{} mkdir another_folder/{}
例子#2

对来自stdin的文件列表进行操作,在这种情况下,.html通过添加.bak扩展名来复制每个文件:

    find . -iname "*.html" | xargs -I{} cp {} {}.bak

xargsMacOS / BSD手册页中

 -I replstr
         Execute utility for each input line, replacing one or more occurrences of
         replstr in up to replacements (or 5 if no -R flag is specified) arguments
         to utility with the entire line of input.  The resulting arguments, after
         replacement is done, will not be allowed to grow beyond 255 bytes; this is
         implemented by concatenating as much of the argument containing replstr as
         possible, to the constructed arguments to utility, up to 255 bytes.  The
         255 byte limit does not apply to arguments to utility which do not contain
         replstr, and furthermore, no replacement will be done on utility itself.
         Implies -x.

Linux xargs手册页

   -I replace-str
          Replace  occurrences of replace-str in the initial-
          arguments with names read from standard input.  Al
          so,  unquoted  blanks do not terminate input items;
          instead the separator  is  the  newline  character.
          Implies -x and -L 1.

1

在处理潜在的未经过滤的输入时,我希望在运行它之前逐行查看整个作业的“拼写”以进行视觉检查(尤其是在破坏性工作,例如清理人们的邮箱时)。

所以我要做的是生成一个参数列表(即用户名),以每行一个记录的方式将其馈送到文件中,如下所示:

johndoe  
jamessmith  
janebrown  

然后,我在中打开列表vim,并使用搜索和替换表达式对其进行处理,直到获得需要执行的完整命令的列表,如下所示:

/bin/rm -fr /home/johndoe  
/bin/rm -fr /home/jamessmith 

这样,如果您的正则表达式不完整,您将看到在哪个命令中有潜在的问题(即/bin/rm -fr johnnyo connor)。这样,您可以撤消正则表达式,然后使用更可靠的版本再次尝试。改名是为此而臭名昭著的,因为很难处理像梵高,奥康纳斯,圣克莱尔,史密斯-韦森这样的边缘案件。

set hlsearch中,对于具有此功能很有用vim,因为它会突出显示所有匹配项,因此您可以轻松地发现匹配项是否不匹配或以非预期方式匹配。

一旦您的正则表达式是完美的并捕获了您可以测试/考虑的所有情况,那么我通常将其转换为sed表达式,以便可以完全自动化以进行另一次运行。

对于输入行数妨碍您进行外观检查的情况,我强烈建议在执行命令之前将命令回显到屏幕(或者更好的是日志),因此,如果出现错误,您可以确切地知道引起该命令的原因它失败了。然后,您可以返回到原始正则表达式并再次进行调整。


0

如果程序忽略管道,但接受文件作为参数,则可以将其指向特殊文件/dev/stdin

我对Java不熟悉,但是这里有一个示例,说明如何使用bash:

$ echo $'pwd \n cd / \n pwd' |bash /dev/stdin
/home/rolf
/

$是bash转换\n为换行符所必需的。我不知道为什么。


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.