我想对不同的应用程序使用相同的管道,例如:
cat my_file | {
cmd1
cmd2
cmd3
}
Cmd1 应该消耗部分输入。 Cmd2 应该消耗另一部分,依此类推。
然而,每个命令都会消耗更多的输入,然后它读取的内容确实需要适当的缓冲。
例如:
yes | nl | {
head -n 10 > /dev/null
cat
} | head -n 10
从第 912 行而不是第 11 行输出。
Tee 不是一个好的选择,因为每个命令都应该消耗部分标准输入。
有没有一种简单的方法可以让它发挥作用?
答案1
您可以使用tee
许多命令来复制命令来处理整个流:
( ( seq 1 10 | tee /dev/fd/5 | sed s/^/line..\ / >&4 ) 5>&1 | wc -l ) 4>&1
line.. 1
line.. 2
line.. 3
line.. 4
line.. 5
line.. 6
line.. 7
line.. 8
line.. 9
line.. 10
10
或者使用 bash 逐行分割:
while read line ;do
echo cmd1 $line
read line && echo cmd2 $line
read line && echo cmd3 $line
done < <(seq 1 10)
cmd1 1
cmd2 2
cmd3 3
cmd1 4
cmd2 5
cmd3 6
cmd1 7
cmd2 8
cmd3 9
cmd1 10
最后有一种运行方式cmd1
,cmd2
并且cmd3
只有一次,1/3 的流为标准输入:
( ( ( seq 1 10 |
tee /dev/fd/5 /dev/fd/6 |
sed -ne '1{:a;p;N;N;N;s/^.*\n//;ta;}' |
cmd1 >&4
) 5>&1 |
sed -ne '2{:a;p;N;N;N;s/^.*\n//;ta;}' |
cmd2 >&4
) 6>&1 |
sed -ne '3{:a;p;N;N;N;s/^.*\n//;ta;}' |
cmd3 >&4
) 4>&1
command_1: 1
command_1: 4
command_1: 7
command_1: 10
Command-2: 2
Command-2: 5
Command-2: 8
command 3: 3
command 3: 6
command 3: 9
为了尝试这个,你可以使用:
alias cmd1='sed -e "s/^/command_1: /"' \
cmd2='sed -e "s/^/Command_2: /"' \
cmd3='sed -e "s/^/Command_3: /"'
如果在同一脚本上,要在不同进程上使用一个流,您可以这样做:
(
for ((i=(RANDOM&7);i--;));do
read line;
echo CMD1 $line
done
for ((i=RANDOM&7;i--;));do
read line
echo CMD2 $line
done
while read line ;do
echo CMD3 $line
done
)
CMD1 1
CMD1 2
CMD1 3
CMD2 4
CMD2 5
CMD2 6
CMD2 7
CMD2 8
CMD2 9
CMD3 10
为此,您可能必须将单独的脚本转换为bash函数能够构建一个整体脚本。
另一种方法可能是确保每个脚本不会输出任何内容标准输出,而不是cat
在每个脚本的末尾添加一个链他们:
#!/bin/sh
for ((i=1;1<n;i++));do
read line
pRoCeSS the $line
echo >output_log
done
cat
最终命令可能如下所示:
seq 1 10 | cmd1 | cmd2 | cmd2
答案2
为了head -n 10
能够从 stdin 上的管道读取 10 行而不是再读取一个字符,它必须一次读取一个字符,以便在最后一个换行符之后不读取任何内容。那将是低效的。
这就是read
当 stdin 不可查找时 shell 内置函数所做的事情。
{
head -n 10 > /dev/null
cat
} < myfile
之所以有效,是因为head
读取一大块数据并lseek
返回到第 10 行末尾之后。这显然不能用管道来完成。
在最近的 GNU 或 FreeBSD 系统上,可以使用或stdio
来告诉某些使用的应用程序一次读取一个字符。stdbuf -i1
stdbuf -i0
然而,这不适用于 GNU head
。不过它可以与 GNU 一起使用sed
,所以你可以这样做:
seq 20 | {
stdbuf -i0 sed -n 10q
cat
}
或者,您可以做的是控制管道上的内容,以便一次最多只有一根线。
例如,在 Linux 上,您可以执行以下操作:
one_line_at_a_time() {
perl -MTime::HiRes=usleep -pe '
BEGIN{$|=1;open F, "<", "/dev/fd/1"; $r=""; vec($r,fileno(F),1) = 1}
usleep(1000) while select($ro=$r,undef,undef,0)'
}
seq 20 | one_line_at_a_time | { head -n 10 > /dev/null; cat; }
该perl
脚本以读取模式打开“/dev/fd/1”,这在 Linux 上会导致连接到 fd 1 (stdout) 的管道的另一端被打开。这样,使用select
,它可以在发送下一行之前检查管道中是否有东西(并休眠直到它被清空)。
当然,这也是非常低效的。