
$ seq 10 | unbuffer -p od -vtc
0000000 1 \n 2 \n 3 \n 4 \n 5 \n 6 \n 7 \n 8 \n
どこに行きました9
か10
?
$ printf '\r' | unbuffer -p od -An -w1 -vtc
\n
なぜ\r
に変更されたのです\n
か?
$ : | unbuffer -p printf '\n' | od -An -w1 -vtc
\r
\n
$ unbuffer -p printf '\n' | od -An -w1 -vtc
\r
\n
何だって?
$ printf foo | unbuffer -p cat
$
なぜ出力がないのでしょうか (そして 1 秒の遅延)?
$ printf '\1\2\3foo bar\n' | unbuffer -p od -An -w1 -vtc
$
なぜ出力されないのですか?
$ (printf '\23'; seq 10000) | unbuffer -p cat
出力されずにハングするのはなぜですか?
$ unbuffer -p sleep 10
自分が入力した内容が表示されないのはなぜですか (また、sleep
読んでいないのに破棄されるのはなぜですか)?
ちなみに、また:
$ echo test | unbuffer -p grep foo && echo found foo
found foo
grep
見つかったfoo
のに、それを含む行が印刷されないのはなぜですか?
$ unbuffer -p ls /x 2> /dev/null
ls: cannot access '/x': No such file or directory
なぜエラーが に発生しなかったのですか/dev/null
?
$ echo ${(l[10000][foo])} | unbuffer -p cat | wc -c
4095
それは次のとおりです:
$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux trixie/sid
Release: n/a
Codename: trixie
$ uname -rsm
Linux 6.5.0-3-amd64 x86_64
$ expect -c 'puts "expect [package require Expect] tcl [info patchlevel]"'
expect 5.45.4 tcl 8.6.13
$ /proc/self/exe --version
zsh 5.9 (x86_64-debian-linux-gnu)
Ubuntu 22.04 または FreeBSD 12.4-RELEASE-p5 でも同じです (ただし、od
コマンドをそこで調整する必要があり、上記の 4095 ではなく 2321 (すべて BEL 文字) が返されます)。
答え1
unbuffer
一部のコマンドの出力が端末デバイスに送信されない場合に、コマンドが行うバッファリングを無効にするツールです。
出力が端末デバイスに送られる場合、コマンドは実際にユーザーが出力をアクティブに見ているものと想定し、出力が利用可能になるとすぐに送信します。正確にはそうではなく、出力は行ベースで送信されます。つまり、出力の準備が整うとすぐに、完了した行が送信されます。
stdout が通常のファイルまたはパイプである場合など、端末デバイスに送信されない場合は、最適化のためにブロックで送信されます。つまり、 が少なくなりwrite()
、パイプの場合は、もう一方の端にあるリーダーを頻繁に起動する必要がなくなるため、コンテキストの切り替えが少なくなります。
ただし、次の場合は次のようになります。
cmd | other-cmd
をターミナルで実行すると、 はother-cmd
何らかのフィルタリング/変換コマンドであり、other-cmd
の stdout は行バッファリングされますが、cmd
はフルバッファリングされます。つまり、対話型ユーザーは、 の出力cmd
( によって変換されたものother-cmd
) をすぐには表示できず、遅延して大きなバッチで表示されます。
unbuffer cmd | other-cmd
cmd
stdout がパイプに送られる場合でも、行ベースのバッファリングを復元するため役立ちます。
cmd
これを行うには、疑似端末で開始し、その疑似端末から来たものをパイプに転送します。そのためcmd
、再びユーザーと通信していると判断し、行バッファリングを行います。
unbuffer
は実際には で書かれていますexpect
。expect
ソースコード内のサンプルスクリプト多くの場合、expect
OS が提供するパッケージに含まれています。
expect
は、疑似端末を使用して端末アプリケーションとの自動対話を実行するために使用されるツールであり、unbuffer
コマンドを記述するのは簡単ですexpect
。冗談で言えば、バグunbuffer
のマニュアルページのセクションには次の内容が記載されています:マニュアルページはプログラムよりも長いです。そして実際、プログラムはただ:
#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh8.6 "$0" ${1+"$@"}
package require Expect
# -*- tcl -*-
# Description: unbuffer stdout of a program
# Author: Don Libes, NIST
if {[string compare [lindex $argv 0] "-p"] == 0} {
# pipeline
set stty_init "-echo"
eval [list spawn -noecho] [lrange $argv 1 end]
close_on_eof -i $user_spawn_id 0
interact {
eof {
# flush remaining output from child
expect -timeout 1 -re .+
return
}
}
} else {
set stty_init "-opost"
set timeout -1
eval [list spawn -noecho] $argv
expect
exit [lindex [wait] 3]
}
ご覧のとおり、またマニュアル ページでも確認されているように、オプションunbuffer
もサポートされています-p
。
ではunbuffer cmd
、擬似端末は cmd の stdout に接続されているだけでなく、stdin と stderr にも接続されています (はexpect
コマンドと対話するためのツールであることに注意してください)。
$ tty; unbuffer readlink /proc/self/fd/{0..2}
/dev/pts/14
/dev/pts/15
/dev/pts/15
/dev/pts/15
これが、unbuffer ls /x 2> /dev/null
エラーが に送信されなかった理由です/dev/null
。stderr は stdout とマージされます。
現在、 はunbuffer
自身の stdin から何も読み取らず、 の stdin に何も送信しませんcmd
。
つまり、A | unbuffer cmd | B
機能しないということです。
ここで-p
(for p
ipe) オプションが登場します。コードに示されているように、 では-p
、の代わりに を、さまざまなチャネルからのデータを処理するアクティブ ループとしてunbuffer
使用します。interact
expect
ステートメントのみを使用するとexpect
、expect
(プログラム/TCL ライブラリ) は擬似端末から送られてくるもの (cmd
つまり、たとえばスレーブ側の stdout または stderr 経由で書き込まれるもの) を読み取り、それを自身の stdout に送信します。
interact
を使用すると、次のようにexpect
なりますが、次のことも行われます。
- 自身の標準入力から読み取ったものを擬似端末に送信する(
cmd
そこで読み取ることができる) - また、
unbuffer
の stdin が端末デバイスである場合は、ローカルを無効にしたモードinteract
にします。raw
echo
これはA | unbuffer -p cmd | B
、A
の出力を の入力として読み取ることができるという点で優れていますcmd
が、次のことが意味します。
unbuffer
は、内部擬似端末を で設定しますset stty_init "-echo"
が、raw
モードでは設定しません。特に、( ( ) / /isig
の処理)、(フロー制御、/ ( )) は無効になっていません。入力が端末デバイスの場合 ( はこのように使用することを想定していますが、 は使用しないでください)、ホスト端末がモードに設定されるため、処理がホスト端末から組み込み擬似端末に移動されるだけなので問題ありません。ただし、 は両方で無効になっているため、入力内容を確認することはできません。ただし、端末デバイスでない場合は、たとえば、入力内の 0x3 バイト ( ) は ( の出力を処理する場合など) SIGINT をトリガーしてコマンドを終了し、 0x19 バイト ( ) はフローを停止します。が無効になっていないことが、が に変更される理由です。^C
\3
^Z
^\
ixon
^Q
^S
\23
expect
interact
unbuffer
raw
echo
^C
printf '\3'
printf '\23'
icrnl
\r
\n
stty -opost
は、 がなければ行う を行いません。これが、による の出力が に変更される-p
理由です。また、入力が端末デバイスの場合、 がそれを に入れるという事実、つまり が無効な場合、 による改行文字出力が に変換されないときに端末出力が壊れるという事実が説明されます。\n
cmd
\r\n
raw
opost
od
\r\n
内部疑似端末では行エディタがまだ有効になっているため、入力からまたは文字が来
cmd
ない限り何も送信されません。これが、何も印刷されない理由です。\r
\n
printf foo | unbuffer -p cat
そして、その行エディタは行のサイズに制限があるので、編集することができます(私のシステムでは4095(Linux)、tty速度の5分の1¹ FreeBSDの場合、次のような問題が発生します。すべての文字をベルに変換するバッファ解除?: のようなダム アプリケーションでキーボードから長すぎる行を入力しようとした場合と同じことが起こります
cat
。Linux では、4094 番目以降の文字はすべて無視されますが、\n
は受け入れられ、行が送信されます。FreeBSD では、38400/5 文字が入力された後は、それ以上の文字は拒否され ( であっても\n
)、BEL が端末に送信されます²。そこで 2321 個の BEL (10001 - 38400/5) が返されるのはそのためです。擬似端末デバイスでは、EOF の処理が不格好です。
unbuffer
の stdin に EOF が見つかると、その情報を に転送できませんcmd
。そのため、 では、が終了したseq 10 | od -vtc
後も、決して届かない擬似端末からの入力を待機し続けます。代わりに、その時点で、すべてが破棄され、強制終了されます (man ページにはその制限について記載されています)。seq
od
od
unbuffer
独自の目的のためには、組み込みの疑似 tty をraw -echo
モードにして、ホスト端末デバイス (存在する場合) をそのままにしておく方がはるかに良いでしょう。ただし、expect
実際にはその動作モードをサポートしておらず、そのために設計されていません。
さて、unbuffer
stdout をバッファリングしないことが目的であれば、stdin と stderr に触れる理由はありません。
実際には、次のようにすることでこれを回避できます。
unbuffer() {
command unbuffer sh -c 4<&0 5>&2 '
exec <&4 4<&- 2>&5 5>&- "$@"' sh "$@"
}
これは、sh
元の stdin と stderr (呼び出しシェルによって fd 4 と 5 を介して渡されますexpect
。内部で明示的に使用する場合のように fd 3 は使用されません) を復元するために使用されます。
それから:
$ echo test | unbuffer readlink /proc/self/fd/{0..2} 2> /dev/null | cat
pipe:[184479]
/dev/pts/16
/dev/null
stdout のみが疑似端末に送信され、バッファリングされません。
そして他のすべての問題は解決します:
$ unbuffer ls /x 2> /dev/null
$ printf '\r' | unbuffer od -An -w1 -vtc
\r
$ : | unbuffer printf '\n' | od -An -w1 -vtc
\n
$ unbuffer printf '\n' | od -An -w1 -vtc
\n
$ printf foo | unbuffer cat
foo
$ printf '\1\2\3foo bar\n' | unbuffer od -An -w1 -vtc
001
002
003
f
o
o
b
a
r
\n
$ (printf '\23'; seq 10000) | unbuffer cat -vte | head
^S1$
2$
3$
4$
5$
6$
7$
8$
9$
10$
$ unbuffer sleep 10
I see what I type
$ I see what I type
zsh: command not found: I
$ echo test | unbuffer grep foo || echo not found
not found
$ echo ${(l[10000][foo])} | unbuffer cat | wc -c
10001
また、必要なのは疑似端末を経由する標準出力を作成することだけである場合、インストールexpect
(TCL インタープリターが必要) は少しやり過ぎのように思えます。cmd
socat
これもできます:
$ echo test | socat -u system:'readlink /proc/self/fd/[0-2]; wc -c',pty,raw - 2> /dev/null | cat
pipe:[187759]
/dev/pts/17
/dev/null
5
(失敗の終了ステータスはログに記録されますが、それ以外の場合はコマンドの終了ステータスは伝播されません)。
シェルzsh
には疑似 tty のサポートも組み込まれており、unbuffer
次のように少しの労力で関数を記述できます。
zmodload zsh/zpty
zmodload zsh/zselect
unbuffer() {
{
return "$(
exec 6>&1 >&5 5>&-
# here fds go:
# 0,3: orig stdin
# 1: orig stdout
# 2,4: orig stderr
# 5: closed
# 6: to return argument
zpty -b unbuffer '
stty raw
exec <&3 3<&- 2>&4 4>&-
# here fds go:
# 0: orig stdin
# 1: pseudo unbuffering tty
# 2: orig stderr
# 3,4,5: closed
# 6: to return argument
"$@" 6>&-
echo "$?" >&6
'
fd=$REPLY
until
zselect -r $fd
zpty -r unbuffer
(( $? == 2 ))
do
continue
done
)"
} 3<&0 4>&2 5>&1
}
これらすべては、新しいターミナルで実行され、新しいセッションでのsocat
アプローチを除き (ctty
およびオプションを使用しない限り) 実行されることに注意してください。したがって、これらの「固定」がホスト ターミナル セッションのバックグラウンドで開始された場合、ホスト ターミナルからの読み取りは停止されません。たとえば、ターミナルからの読み取りがバックグラウンド ジョブで終了し、大混乱を引き起こします。setid
unbuffer
cmd
unbuffer cat&
¹ 65536まで上限。スピード擬似端末の場合は関係ありませんが、宣伝されているものがあり、私がテストしたFreeBSDシステムではデフォルトで38400であることがわかりました。速度は端末制御の速度からコピーされるため、そのバッファを拡大するために呼び出す前に(AFAICTの最大値)expect
を実行できます。しかし、それでも10000文字の大きな行全体を取得できない場合があります。stty speed 115200
unbuffer
ドライバーコードで説明4096バイトしか返されないことがわかります。これは、最初の呼び出しで要求されたunbuffer -p cat
量と同じで、ttyドライバが入力行から返した量と同じだからです。cat
read()
残りは捨てた(!) を に置き換えるとunbuffer -p dd bs=65536
、完全な行 (つまり、115200/5 バイトまで) が表示されます。
² スクリプト内でset stty_init "-echo"
を に置き換えることでこれらの BEL を回避できますが、データの取得には役立ちません。set stty_init "-echo -imaxbel"
unbuffer