管道,{列表;}仅适用于某些程序


13

对于此类不可预测的行为,需要高级用户的解释:

ps -eF | { head -n 1;grep worker; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root       441     2  0     0     0   2 paź15 ?       00:00:00 [kworker/2:1H]

一切看起来都不错,而

ls -la / | { head -n 1;grep sbin; }

仅显示来自的输出 head

...我想过stdout 2>&1,对我也不起作用,这很奇怪,没有任何解释或建议如何处理?


1
最后一个应该打印出所有内容。在headgrep做什么也没有。
jordanm

你是对的。但是,取而代之的是,为什么ps -eF在ls -la /不起作用时起作用?
2013年

Answers:


9

我进行了一些使用调查strace,这似乎是由于管道左侧的程序将其写入终端的方式所致。ls执行该命令时,它将所有数据写入一个write()。这导致head消耗掉所有的stdin。

另一方面ps,批量写入数据,因此只有第一个write()被消耗head,然后才存在。以后的调用write()将转到新产生的grep过程。

这意味着,如果您要尝试的过程grep没有在第一个中发生,那么它将无法正常工作write(),因为grep它无法看到所有数据(它看到的甚至少于减去第一行的数据)。

这是尝试在系统上为pid 1进行grep的示例:

$ ps -eF | { head -n2; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root         1     0  0  1697  3768   2 Oct03 ?        00:00:03 /lib/systemd/systemd
$ ps -eF | grep '/lib/systemd/systemd$'
root         1     0  0  1697  3768   2 Oct03 ?        00:00:03 /lib/systemd/systemd
$ ps -eF | { head -n1; grep '/lib/systemd/systemd$'; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD

您的ps -eF示例仅是偶然的。


非常感谢您进行全面而详尽的说明
ast

1
实际上,这更多的是种族条件。只是执行多个write()呼叫的速度较慢。如果调用的head执行速度较慢read()(例如管道缓冲区中包含了所有数据),它将在ls和之间表现出相同的行为ps
Patrick

6

这是由glibc中的缓冲引起的。如果ls输出在一个内部缓冲区中,则仅传递给head。对于ps -eF,输出会更大,因此一旦head完成,以下代码grep将获得的其余部分(但不是全部)ps

您可以通过取消缓冲管道来摆脱它-例如,使用sed -u(我不确定它不是GNU扩展):

$ ls -al / | sed -u "#" | { head -n 1; grep bin; }
total 76
drwxr-xr-x   2 root root  4096 Oct  2 21:52 bin
drwxr-xr-x   2 root root  8192 Oct  3 01:54 sbin

4

正在发生的事情是head -n 1读取多于1行。为了获得最佳吞吐量,head读取字节块,因此它可能一次读取1024个字节,然后在这些字节中查找第一个换行符。由于换行符可能发生在该1024字节的中间,因此其余数据将丢失。它不能放回管道上。因此,下一个执行的进程仅获得1025字节及以上字节。

您的第一个命令恰好成功,因为该kworker过程在head读取的第一个块之后。

为了使其正常工作,head必须一次读取1个字符。但这非常慢,所以没有。
有效执行此类操作的唯一方法是让一个进程同时执行“ head”和“ grep”。

这是执行此操作的2种方法:

echo -e '1\n2\n3\n4\n5' | perl -ne 'print if $i++ == 0 || /4/'

要么

echo -e '1\n2\n3\n4\n5' | awk '{if (NR == 1 || /4/) print }'

还有更多...


是的,我知道“ awk的方式”可以完成此任务,但是想知道为什么{list; }。感谢您澄清其运作方式。以上所有答案给我留下了深刻的印象
2013年

2

如果只需要第一行或第二行,则可以使用以下类型的技巧,并且可以避免由于使用两个不同的命令读取输出流而导致的缓冲问题:

$ ps -eF   | { IFS= read -r x ; echo "$x" ; grep worker; }
$ ls -la / | { IFS= read -r x ; echo "$x" ; grep sbin; }

read是内置于壳和不消耗输入的整个缓冲器只是输出一行,因此,使用read叶片的输出的所有其余为以下命令。

如果要强调使用两个不同命令的示例所显示的缓冲问题,请sleep在其上添加a 以消除计时问题,并允许左侧的命令生成所有输出,然后右侧的命令尝试读取任何内容。它:

$ ps -eF   | { sleep 5 ; head -n 1 ; grep worker; }
$ ls -la / | { sleep 5 ; head -n 1 ; grep sbin; }

现在,以上两个示例均以相同的方式失败- head读取输出的整个缓冲区只是产生一行,而该缓冲区对以下对象不可用grep

通过使用一些对输出行编号的示例,您可以更清楚地看到缓冲问题,从而可以知道缺少哪些行:

$ ps -eF          | cat -n | { sleep 5 ; head -n 1 ; head ; }
$ ls -la /usr/bin | cat -n | { sleep 5 ; head -n 1 ; head ; }

解决缓冲问题的一种简单方法是使用seq生成数字列表。我们可以轻松判断出哪些数字丢失了:

$ seq 1 100000    | { sleep 5 ; head -n 1 ; head ; }
1

1861
1862
1863
1864
1865
1866
1867
1868
1869

我的技巧解决方案使用Shell读取并回显第一行,即使添加了睡眠延迟,也可以正常工作:

$ seq 1 100000 | { sleep 5 ; IFS= read -r x ; echo "$x" ; head ; }
1
2
3
4
5
6
7
8
9
10
11

下面是显示head缓冲问题的完整示例,显示了如何 head消耗输出的整个缓冲区,而每次仅产生其五行。该消耗的缓冲区不适用于head序列中的下一个 命令:

$ seq 1 100000 | { sleep 5 ; head -5 ; head -5 ; head -5 ; head -5 ; }
1
2
3
4
5

1861
1862
1863
1864
499
3500
3501
3502
3503
7
5138
5139
5140
5141

查看1861上面的数字,我们可以head通过计算to 的seq输出1来 计算正在使用的缓冲区的大小1860

$ seq 1 1860 | wc -c
8193

我们看到,head通过一次读取完整的8KB(8 * 1024字节)的管道输出来缓冲,甚至只产生几行自己的输出。

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.