2

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

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

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

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

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

(这个问题是我之前的一个清理版本,基于指出不明确之处的回复)

4

8 回答 8

10

对于此示例,您应该按照 semiuseless 的建议使用 awk。

但一般来说,要让 N 个任意程序读取单个输入流的副本,您可以使用teebash 的过程输出替换运算符:

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

请注意,这>(command)是一个 bash 扩展。

于 2009-09-24T15:39:50.950 回答
5

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

如果你不想写脚本,又想并行运行,bash 支持subshel​​ls的概念,它们可以并行运行。通过将您的命令放在括号中,您可以同时运行您的 greps(或其他),例如

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

请注意,在上面你cat可能是多余的,因为grep需要一个输入文件参数。

您可以(相反)通过不同的流重定向输出。您不仅限于 stdout/stderr,还可以根据需要分配新的流。除了将您带到此处的示例之外,我无法提供更多建议

于 2009-06-12T10:36:26.197 回答
2

我喜欢斯蒂芬使用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
于 2009-06-12T12:30:14.487 回答
1

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

awk '/Coffee/ { print "Coffee" } /Tea/ { print "Tea" > "/dev/stderr" }' inputfile > coffee.file.txt 2> tea.file.txt
于 2009-06-12T11:07:27.383 回答
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

使用包含空格的模式仍有待解决。

于 2009-06-12T19:24:17.923 回答
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 {}
$
于 2009-06-24T01:00:32.890 回答
0

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

  • 是空格/逗号分隔吗?
  • 您是否在特定“列”上有项目描述,其中列由空格、逗号或其他分隔符定义?

如果你能负担得起多次 grep 运行,这将起作用,

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

等等。

于 2009-06-12T10:35:50.690 回答
0

假设您的输入不是无限的(例如您从未计划关闭的网络流),我可能会考虑使用子外壳将数据放入临时文件,然后使用一系列其他子外壳来读取它。我没有对此进行测试,但它可能看起来像这样 { cat inputstream > tempfile }; { grep 茶临时文件 > tea.txt }; { grep 咖啡临时文件 > coffee.txt};

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

于 2009-06-12T13:43:08.150 回答