パイプ、{リスト; }一部のプログラムでのみ機能する


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
最後のものはすべてを印刷する必要があります。headそしてgrepそこに何もしません。
ヨルダン

はい、あなたは正しいです。しかし、その代わりに、ps -eFがls -la / notで動作するのはなぜですか?
AST

回答:


9

を使用していくつかの調査をstrace行いましたが、それはパイプラインの左側のプログラムがターミナルに書き込む方法に起因するようです。ときにlsコマンドが実行され、それは単一のすべてのデータを書き込みますwrite()。これによりhead、すべての標準入力が消費されます。

一方、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例は偶然にしか機能しません。


偉大かつ包括的なexpalnationのおかげで多くの
AST

1
実際、それは競合状態です。複数のwrite()呼び出しの実行が遅いというだけです。場合はhead、それの実行が遅れたread()コール(パイプバッファはその中のすべてのデータを持っていたことなどを)、それは両方の間で同じ挙動を示すであろうlsps
パトリック

6

これは、glibcのバッファリングが原因です。ls出力の場合、1つの内部バッファーにあり、そのままに渡され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の方法」を知っていますが、{リスト; }。仕組みを明確にしていただきありがとうございます。私は上記のすべての回答に感銘を受けています
AST

2

最初の1行または2行だけが必要な場合は、次のタイプのトリックが機能し、2つの異なるコマンドを使用して出力ストリームを読み取ることによるバッファリングの問題を回避します。

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

readシェルに内蔵されているので、使用して、ちょうど1出力ラインに入力の全体のバッファを消費しませんread、次のコマンドの葉に、出力のすべての残りの部分を。

2つの異なるコマンドを使用する例で示されているバッファリングの問題を強調したい場合はsleep、それらに追加してタイミングの問題を解消し、右側のコマンドがいずれかの読み取りを試みる前に左側のコマンドがすべての出力を生成できるようにしますそれ:

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

さて、上記の例は両方とも同じように失敗しheadます-1行を生成するためだけに出力のバッファー全体を読み取り、そのバッファーは以下では使用できません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

シェルを使用して最初の行を読み取り、エコーする私のトリックソリューションは、スリープ遅延を追加しても正しく機能します。

$ 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毎回5行を生成するために出力のバッファ全体を消費する方法を示し ています。その消費されたバッファは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上記の数値を見ると、to からheadseq出力をカウントすることで、使用されているバッファーのサイズを計算できます:11860

$ seq 1 1860 | wc -c
8193

headパイプライン出力の8KB(8 * 1024バイト)を一度に読み取ってバッファリングしていることがわかります。独自の出力をほんの数行生成することもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.