パイプのバッファリングをオフにします


395

2つのコマンドを呼び出すスクリプトがあります。

long_running_command | print_progress

long_running_commandプリントは進歩が、私はそれに不満です。print_progressより良いものにするために使用しています(つまり、進行状況を1行で出力します)。

問題:stdoutにパイプを接続すると、4Kバッファーもアクティブになります。素敵な印刷プログラムには何も...何も...何も... たくさんありません... :)

4Kバッファーを無効にするにはどうすればよいですかlong_running_command(いいえ、ソースがありません)?


1
したがって、パイピングなしでlong_running_commandを実行すると、進捗の更新を適切に確認できますが、パイピングするとバッファリングされますか?

1
はい、それはまさに起こることです。
アーロンディグラ09年

20
バッファリングを制御する簡単な方法ができないことは、何十年もの間問題でした。たとえば、以下を参照してください。marc.info/?l=glibc-bug&m=98313957306297&w=4 basicly言う「ここで私はこれをやってarsedすることができないとは、私の立場を正当化するためにいくつかの拍手トラップだ」


1
十分なデータを待っている間に遅延を引き起こすのは、実際にはパイプではなくstdioです。パイプには容量がありますが、パイプにデータが書き込まれるとすぐに、もう一方の端ですぐに読み取る準備ができます。
サムワトキンス

回答:


254

unbufferコマンド(expectパッケージの一部として提供されます)を使用できます。例えば

unbuffer long_running_command | print_progress

unbufferlong_running_command疑似端末(pty)を介して接続します。これにより、システムはそれを対話型プロセスとして扱い、パイプラインで4-kiBバッファリングを使用せず、遅延の原因となる可能性があります。

より長いパイプラインの場合、各コマンドのバッファリングを解除する必要がある場合があります(最後のコマンドを除く)。

unbuffer x | unbuffer -p y | z

3
実際、インタラクティブプロセスに接続するためにptyを使用することは、一般的に期待されています。

15
unbufferへの呼び出しをパイプライン化するときは、-p引数を使用して、unbufferがstdinから読み取るようにする必要があります。

26
Debianシステムで、これが呼び出されます。注意してくださいexpect_unbufferとしているexpect-devパッケージではなく、expectパッケージ
bdonlan

4
@bdonlan:少なくともUbuntu(Debianベース)では、expect-dev両方unbufferを提供しますexpect_unbuffer(前者は後者へのシンボリックリンクです)。リンクはexpect 5.44.1.14-1(2009)以降に利用可能です。
jfs

1
注:Ubuntu 14.04.xシステムでは、expect-devパッケージにも含まれています。
アレクサンドルマゼル

462

この猫の皮を剥ぐ別の方法stdbufは、GNU Coreutilsの一部であるプログラムを使用することです(FreeBSDにも独自のプログラムがあります)。

stdbuf -i0 -o0 -e0 command

これにより、入力、出力、エラーのバッファリングが完全にオフになります。アプリケーションによっては、パフォーマンス上の理由から、ラインバッファリングの方が適している場合があります。

stdbuf -oL -eL command

動的にリンクされたアプリケーションのstdioバッファリング(printf()fputs()...)に対してのみ機能し、そのアプリケーションが標準ストリームのバッファリングをそれ以外の方法で調整しない場合にのみ機能することに注意してください。


6
「unbuffer」は、パッケージ内のUbuntuにインストールする必要があります
。expect

2
これは、ロギングをバッファリング解除するデフォルトのraspbianインストールでうまく機能します。見つかりませんでしたが、sudo stdbuff … command作品を見つけましたstdbuff … sudo command
natevw

20
で設定されたデフォルトを上書きするため、@ qdii stdbufは動作しません。のマニュアルページを参照してください。teeteestdbufstdbuf
14年

5
@lepe奇妙なことに、unbufferはx11とtcl / tkに依存しています。つまり、それらを使用せずにサーバーにインストールする場合、実際には80 MBを超える必要があります。
jpatokal 14

10
@qdii stdbufLD_PRELOADメカニズムを使用して、動的にロードされた独自のライブラリを挿入しますlibstdbuf.so。これは、これらの種類の実行可能ファイルでは動作しないことを意味します。setuidまたはファイル機能が設定され、静的にリンクされ、標準のlibcは使用されません。これらのケースでは、とのソリューションを使用することをお勧めしますunbuffer/ script/ socatsetuid / capabilitiesを指定したstdbufも参照してください。
pabouk

75

の行バッファリング出力モードをオンにするもう1つの方法は、疑似端末(pty)で実行するコマンドlong_running_commandを使用するscriptことlong_running_commandです。

script -q /dev/null long_running_command | print_progress      # FreeBSD, Mac OS X
script -c "long_running_command" /dev/null | print_progress    # Linux

15
+1素敵なトリック。これscriptは非常に古いコマンドなので、すべてのUnixライクなプラットフォームで利用できるはずです。
アーロンディグラ

5
-qLinux でも必要ですscript -q -c 'long_running_command' /dev/null | print_progress
。– jfs

1
スクリプトがから読み取るように思われるため、少なくとも対話型端末から起動した場合、バックグラウンドでそのようなスクリプトstdinを実行することはできませんlong_running_command。回避策として、を使用し/dev/nulllong_running_commandいないため、からstdinをリダイレクトできましたstdin
haridsv

1
Androidでも動作します。
not2qubit 14

3
1つの重大な欠点:ctrl-zが機能しなくなりました(つまり、スクリプトを中断できません)。これは、たとえば次の方法で修正できます。sudoスクリプト-c / usr / local / bin / ec2-snapshot-all / dev / null | ts、プログラムと対話できないことを気にしない場合。
rlpowell

66

以下のためにgrepsedそしてawkあなたはラインバッファリングする出力を強制することができます。次を使用できます。

grep --line-buffered

出力を強制的に行バッファリングします。デフォルトでは、標準出力が端末である場合、出力は行バッファリングされ、それ以外の場合はブロックバッファリングされます。

sed -u

出力行をバッファリングします。

詳細については、このページを参照してください:http : //www.perkin.org.uk/posts/how-to-fix-stdio-buffering.html


51

出力が端末に送られないときにlibcがバッファリング/フラッシュを変更することに問題がある場合は、socatを試してください。ほぼすべての種類のI / Oメカニズム間で双方向ストリームを作成できます。それらの1つは、疑似ttyに話す分岐プログラムです。

 socat EXEC:long_running_command,pty,ctty STDIO 

それは何ですか

  • 擬似ttyを作成します
  • stty / stdoutとしてptyのスレーブ側でlong_running_commandをフォークします
  • ptyのマスター側と2番目のアドレス(ここではSTDIO)の間に双方向ストリームを確立します

これにより、と同じ出力が得られる場合はlong_running_command、パイプで続行できます。

編集:すごいアンバッファーの答えが表示されませんでした!とにかく、socatはとにかく素晴らしいツールなので、この答えをそのままにしておくかもしれません


1
...そして、私はsocatを知りませんでした-おそらくnetcatのように見えます。;)ありがとう、+ 1。

3
私が使用したいsocat -u exec:long_running_command,pty,end-close -ここに
ステファンChazelas

20

使用できます

long_running_command 1>&2 |& print_progress

問題は、スクリーンへの標準出力時にlibcがラインバッファし、ファイルへの標準出力時にフルバッファすることです。しかし、stderrにはバッファがありません。

パイプバッファの問題ではないと思います。libcのバッファポリシーがすべてです。


あなたが正しい; 私の質問はまだです:再コンパイルせずにlibcのバッファポリシーにどのように影響を与えることができますか?
アーロンディグラ14

@StéphaneChazelasfd1はstderrにリダイレクトされます
王HongQin

@StéphaneChazelas私はあなたの議論のポイントを取得できません。
plz

3
OK、起こっているのは両方zsh|&cshからの適応に由来する)とbashcmd1 >&2 |& cmd2そうするとfd 1と2の両方が外側の標準出力に接続されるということです。そのため、外側のstdoutがターミナルである場合にバッファリングを防止しますが、これは出力がパイプを通過しないためです(したがって、print_progress何も出力されません)。そのためlong_running_command & print_progress、print_progress stdinがライターを持たないパイプであること以外は同じです。とls -l /proc/self/fd >&2 |& cat比較して確認できls -l /proc/self/fd |& catます。
ステファンシャゼル

3
それ|&2>&1 |、文字通り、の略だからです。そうcmd1 |& cmd2ですcmd1 1>&2 2>&1 | cmd2。したがって、fd 1とfd 2は両方とも元のstderrに接続され、パイプには何も書き込まれません。(s/outer stdout/outer stderr/g以前のコメントで)。
ステファンシャゼル

11

標準出力が端末に書き込まれると、デフォルトで行バッファリングされます-改行が書き込まれると、行は端末に書き込まれます。標準出力がパイプに送信されると、完全にバッファリングされます。したがって、データは、標準I / Oバッファがいっぱいになるとパイプラインの次のプロセスにのみ送信されます。

それが問題の原因です。パイプに書き込むプログラムを変更せずに修正するためにできることがたくさんあるかどうかはわかりません。フラグsetvbuf()付きの関数を使用して、_IOLBF無条件stdoutに行バッファーモードにできます。しかし、プログラムでそれを強制する簡単な方法は見当たりません。または、プログラムはfflush()適切なポイント(出力の各行の後)で実行できますが、同じコメントが適用されます。

パイプを擬似端末に置き換えた場合、標準I / Oライブラリは出力が端末であると見なし(端末の一種であるため)、ラインバッファを自動的に処理すると考えられます。しかし、それは物事を処理する複雑な方法です。


7

これは古い質問であり、すでに多くの回答がありましたが、バッファの問題を回避したい場合は、次のようなものを試してください:

stdbuf -oL tail -f /var/log/messages | tee -a /home/your_user_here/logs.txt

これにより、ログがリアルタイムで出力され、ログがlogs.txtファイルに保存され、バッファはtail -fコマンドに影響を与えなくなります。


4
これは2番目の答えのように見えます:-/
アーロンディグラ

2
stdbufはgnu coreutilsに含まれています(最新バージョン8.25で検証済み)。これが組み込みLinuxで機能することを確認しました。
zhaorufei

stdbufのドキュメントから、 NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for example) then that will override corresponding changes by 'stdbuf'.
shrewmouse

6

問題はパイプにあるとは思わない。長時間実行しているプロセスが自身のバッファを十分な頻度でフラッシュしていないようです。パイプのバッファーサイズを変更することは、それを回避するためのハックになりますが、カーネルを再構築しないとその可能性はないと思います。


18
根本的な原因は、stdoutがttyでない場合、libcが4kバッファリングに切り替えることです。
アーロンディグラ09年

5
それはとても面白いです!パイプはバッファリングを引き起こさないからです。これらはバッファリングを提供しますが、パイプから読み取る場合、使用可能なデータを取得できます。パイプ内のバッファを待つ必要はありません。そのため、犯人はアプリケーションのstdioバッファリングになります。

3

ここのこの投稿によると、パイプのulimitを1つの512バイトブロックに減らすことができます。それは確かにバッファリングをオフにしませんが、まあ、512バイトは4Kよりもはるかに少ないです:3


3

chadの答えと同様に、次のような小さなスクリプトを書くことができます。

# save as ~/bin/scriptee, or so
script -q /dev/null sh -c 'exec cat > /dev/null'

次に、このscripteeコマンドをの代わりとして使用しteeます。

my-long-running-command | scriptee

残念ながら、そのようなバージョンをLinuxで完全に動作させることはできないので、BSDスタイルのUNIXに限定されているようです。

Linuxでは、これは近いですが、(Enterキーを押すまでなど)終了してもプロンプトは返されません...

script -q -c 'cat > /proc/self/fd/1' /dev/null

なぜそれが機能するのですか?「スクリプト」はバッファリングをオフにしますか?
アーロンディグラ16

@Aaron Digulla:scriptターミナルをエミュレートするので、はい、バッファリングをオフにします。また、送信された各文字をエコーバックします。このcatため/dev/null、この例では送信されます。内部scriptで実行されているプログラムに関する限り、対話型セッションと通信しています。expectこの点では似ていると思いますが、scriptおそらくベースシステムの一部です。
JWD

私が使用する理由teeは、ストリームのコピーをファイルに送信するためです。ファイルはどこに指定されscripteeますか?
ブルーノブロノスキー

@BrunoBronosky:あなたは正しい、それはこのプログラムの悪い名前です。本当に「ティー」操作を行っているわけではありません。元の質問ごとに、出力のバッファリングを無効にしているだけです。「scriptcat」と呼ばれるべきかもしれません(連結もしていませんが...)。とにかく、catコマンドをtee myfile.txtに置き換えることができ、望む効果が得られるはずです。
jwd

2

私はこの賢い解決策を見つけました: (echo -e "cmd 1\ncmd 2" && cat) | ./shell_executable

これがトリックです。cat(EOFまで)追加の​​入力を読み取りecho、の入力ストリームに引数を入力した後、それをパイプに渡しshell_executableます。


2
実際にcatは、の出力は表示されませんecho。サブシェルで2つのコマンドを実行するだけで、両方の出力がパイプに送信されます。サブシェルの2番目のコマンド( 'cat')は、親/外部の標準入力から読み取ります。これが機能する理由です。
アーロンディグラ

0

よると、このパイプのバッファサイズはカーネルに設定されているように見えると変えるためにあなたのカーネルを再コンパイルする必要がありません。


7
それは別のバッファーだと思います。
サミュエルエドウィン区
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.