在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 的清理版本,基于指出不清楚的回复)
答案 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
!
#!/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
#!/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};
如果您的输入流的大小没有限制,我不确定文件的优雅解决方案会变得太大。