Skip to content
Rich Mikan edited this page Mar 27, 2020 · 2 revisions

stdbufが効かないコマンド(PerlやPython等)にもラインバッファリングをさせる方法

英語版はこちら(English document is here.)

結論から言うと…

  1. 次のファイルをダウンロードせよ。

https://github.com/ShellShoccar-jpn/misc-tools/blob/master/C_SRC/ptw.c、またはダイレクトリンク

  1. それを次のようにコンパイルせよ。
$ cc -O3 -o ptw ptw.c

これにより、ptw(疑似端末ラッパー)というコマンドが生成される。

  1. 次のコードを実行し、比べてみることにより、ptwが正しく動作することを確かめてみよ。
# stdbufを使う場合(最後に一気に表示、つまり失敗する)
$ (i=0; while [ $i -lt 2 ]; do echo $i; sleep 1; i=$((i+1)); done; echo $i) | stdbuf -o L perl -pe 's///' | cat

# ptwを使う場合(1秒間隔で表示、つまり成功する)
$ (i=0; while [ $i -lt 2 ]; do echo $i; sleep 1; i=$((i+1)); done; echo $i) | ./ptw perl -pe 's///' | cat

stdbufがバッファリングモード変更に失敗する理由

stdbufの効かないコマンドの多くは自分自身でバッファリングモードを管理しています。そしてそれらのほとんどは、バッファリングモードをユーザーが変更できるようになっています。例をいくつか示します。

  • Perl
    • Perlをラインバッファリングモードで動かしたい場合は、組み込み変数"$|"に1を代入します。
  • Python
    • "-u"オプション付きでpythonを起動すると、バッファリングを完全にしなくなります。

stdbufコマンドがバッファリングモードを切り替えるタイミングは、対象コマンドの起動前です。つまり、stdbufはまずバッファリングモードを切り替えてからコマンドを起動します。したがって、もしターゲットコマンドが起動した後で独自にバッファリングモードを初期化すると、stdbufが設定したものは上書きされてしまいます。

ptwがそのようなコマンドに対してもバッファリングモードを変更できる理由

標準入出力ライブラリー<stdio.h>に収録されている標準出力へ書き出す各種関数は、そのライブラリーをインクルードしたコマンドがパイプラインの開始端や途中にあるか、あるいは終端にあるかでデフォルトの動作を替えます。前者の場合はフルバッファリングモード、後者の場合はラインバッファリングモードになります。そしてバッファリングモードを自己管理しているコマンドの多くも、この慣習に従います。

そこでptwコマンドは、疑似ターミナル(PTY)を使ってターゲットコマンドを騙します。 まずptwは、標準出力に接続されたPTYを作成し、次にターゲットコマンド用の標準出力をPTYにすり替え、ターゲットコマンドを起動します。 ターゲットコマンドは、自分に与えられた標準出力がターミナルのように見えるため、自分がパイプラインの最後にあると思い込みます。 その結果、そのコマンドはラインバッファリングモードでデータを送信します。 これがptwのメカニズムです。

技術的制約

このコマンドをもってしても、次のようなコマンドには効果がありません。

  • <stdio.h>ライブラリーを使用していないコマンド。そもそもptwは、<stdio.h>やそれに類する動作をするバッファーの習性を逆手に取って騙すという動作原理をとるからです。
  • <stdio.h>ライブラリーを使用してはいるが、自分がパイプラインのどこに配置されているのかを気にせず、常にフルバッファリングモードにするコマンド。mawkなどがその例で、これは"-W interactive"というオプションが与えられない限り、常にフルバッファリングモードで動作します。