我可以链接多个命令并使所有命令从stdin获取相同的输入吗?

时间:2009-06-12 10:26:37

标签: bash unix shell awk

在bash中,有没有办法链接多个命令,所有命令都从stdin获取相同的输入?也就是说,一个命令读取stdin,进行一些处理,将输出写入文件。链中的下一个命令获得与第一个命令相同的输入。等等。

例如,考虑通过过滤内容将大文本文件拆分为多个文件。像这样:

cat food_expenses.txt | grep "coffee" > coffee.txt | grep "tea" > tea.txt | grep "honey cake" > cake.txt

这显然不起作用,因为第二个grep获取第一个grep的输出,而不是原始文本文件。我尝试插入 tee的,但这没有帮助。是否有一些bash魔法会导致第一个grep将其输入发送到管道而不是输出?

顺便说一句,拆分文件就是一个简单的例子。考虑拆分(通过模式搜索进行文件传输)来自网络的连续实时文本流,并将输出写入不同的命名管道或套接字。我想知道是否有一种简单的方法可以使用shell脚本来完成它。

(这个问题是我的 earlier one 的清理版本,基于指出不清楚的回复)

8 个答案:

答案 0 :(得分:10)

对于这个例子,你应该使用awk作为半无用的建议。

但通常要让N个任意程序读取单个输入流的副本,您可以使用tee和bash的进程输出替换运算符:

tee <food_expenses.txt \
  >(grep "coffee" >coffee.txt) \
  >(grep "tea" >tea.txt) \
  >(grep "honey cake" >cake.txt)

请注意,>(command)是bash扩展名。

答案 1 :(得分:5)

显而易见的问题是,为什么要在一个命令中执行此操作?

如果你不想编写脚本,并且想要并行运行东西,bash支持子shell 的概念,它们可以并行运行。通过将命令放在括号中,您可以同时运行greps(或其他),例如

$ (grep coffee food_expenses.txt > coffee.txt) && (grep tea food_expenses.txt > tea.txt) 

请注意,在上面,cat可能是多余的,因为grep采用输入文件参数。

您可以(改为)通过不同的流重定向输出。您不限于stdout / stderr,但可以根据需要分配新流。除了指导示例here

之外,我无法提供更多相关建议

答案 2 :(得分:2)

我喜欢Stephen's使用awk代替grep的想法。

它不漂亮,但这是一个使用输出重定向来保持所有数据流过stdout的命令:

cat food.txt | 
awk '/coffee/ {print $0 > "/dev/stderr"} {print $0}' 
    2> coffee.txt | 
awk '/tea/ {print $0 > "/dev/stderr"} {print $0}' 
    2> tea.txt

正如您所看到的,它使用awk将与'coffee'匹配的所有行发送到stderr,并将所有行与stdout的内容无关。然后将stderr输入到文件中,并使用“茶”重复该过程。

如果您想在每个步骤中过滤掉内容,可以使用:

cat food.txt | 
awk '/coffee/ {print $0 > "/dev/stderr"} $0 !~ /coffee/ {print $0}' 
    2> coffee.txt | 
awk '/tea/ {print $0 > "/dev/stderr"} $0 !~ /tea/ {print $0}' 
    2> tea.txt

答案 3 :(得分:1)

您可以使用awk分割为最多两个文件:

awk '/Coffee/ { print "Coffee" } /Tea/ { print "Tea" > "/dev/stderr" }' inputfile > coffee.file.txt 2> tea.file.txt

答案 4 :(得分:1)

以下是两个bash脚本没有awk 。第二个甚至不使用grep

使用grep:

#!/bin/bash
tail -F food_expenses.txt | \
while read line
do
    for word in "coffee" "tea" "honey cake"
    do
        if [[ $line != ${line#*$word*} ]]
        then
            echo "$line"|grep "$word" >> ${word#* }.txt # use the last word in $word for the filename (i.e. cake.txt for "honey cake")
        fi
    done
done

没有grep:

#!/bin/bash
tail -F food_expenses.txt | \
while read line
do
    for word in "coffee" "tea" "honey cake"
    do
        if [[ $line != ${line#*$word*} ]] # does the line contain the word?
        then
            echo "$line" >> ${word#* }.txt # use the last word in $word for the filename (i.e. cake.txt for "honey cake")
        fi
    done
done;

修改

这是一个AWK方法:

awk 'BEGIN {
         list = "coffee tea"; 
         split(list, patterns)
     }
     {
         for (pattern in patterns) {
             if ($0 ~ patterns[pattern]) {
                 print > patterns[pattern] ".txt"
             }
         }
     }' food_expenses.txt

处理包含空格的模式仍有待解决。

答案 5 :(得分:1)

我不清楚为什么需要在不同的步骤中完成过滤。单个awk程序可以扫描所有传入的行,并将适当的行分派给各个文件。这是一个非常简单的调度,可以提供多个辅助命令(即监视输出文件以获取新输入的持久进程,或者文件可以是提前设置并由awk进程写入的套接字。)。

如果有理由让每个过滤器看到每一行,那么只需删除“下一个”;语句,每个过滤器都会看到每一行。

$ cat split.awk
BEGIN{}
/^coffee/ {
    print $0 >> "/tmp/coffee.txt" ;
    next;
}
/^tea/ {
    print $0 >> "/tmp/tea.txt" ;
    next;
}
{ # default
    print $0 >> "/tmp/other.txt" ;
}
END {}
$

答案 6 :(得分:0)

您可以编写一个简单的AWK脚本来一次性执行此操作。你能再描述一下你的文件格式了吗?

  • 空格/逗号是否分开?
  • 您是否在特定“列”上有项目描述,其中列由某些分隔符(如空格,逗号或其他内容)定义?

如果你能负担多次grep运行,这将有效,

grep coffee food_expanses.txt> coffee.txt
grep tea food_expanses.txt> tea.txt

等等。

答案 7 :(得分:0)

假设您的输入不是无限的(就像您从未计划关闭的网络流一样)我可能会考虑使用子shell将数据放入临时文件,然后使用一系列其他子shell来读取它。我没有测试过这个,但也许它看起来像这样     {cat inputstream&gt; tempfile};     {grep tea tempfile&gt; tea.txt};     {grep coffee tempfile&gt; coffee.txt};

如果您的输入流的大小没有限制,我不确定文件的优雅解决方案会变得太大。