$ 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はラインバッファリングされますが、sは完全にバッファリングされます。つまり、インタラクティブユーザーは、出力が利用可能になるとすぐに(変換を介して)cmd
出力を表示できないことを意味します。しかし、遅れてボリュームが高い。cmd
other-cmd
unbuffer cmd | other-cmd
cmd
標準出力がパイプに入ってもラインベースのバッファリングを復元するので便利です。
これを行うには、cmd
疑似端末から開始し、その疑似端末の内容をパイプに渡します。したがって、cmd
ユーザーと再会話し、ラインバッファリングを実行すると考えてください。
unbuffer
実際にはに記録されていますexpect
。expect
サンプルスクリプトのソースコード、通常はオペレーティングexpect
システムによって提供されるソフトウェアパッケージに含まれています。
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
これは、エラーがstderrunbuffer ls /x 2> /dev/null
に送信されず、stdoutがマージされる理由を説明します。/dev/null
これで、unbuffer
独自の標準入力から何も読み取られず、独自の標準入力に送信されることもありませんcmd
。
A | unbuffer cmd | B
これは機能しないことを意味します。
これが-p
(p
ipe用)オプションが入ってくるところです。コードに示すように、withは別のチャンネルのデータを処理するアクティブループとして代わりに使用され-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
(()//処理)、(フロー制御、/())は無効になりません。入力がターミナルデバイスの場合(代わりにsが使用される方法)は大丈夫です。ホストデバイスがモードになっているからです。これは、両方の端末が無効になっていることを除いて、処理がホスト端末から組み込み擬似端末に送信されることを意味します。だからあなたはあなたが入力した内容を見ることができません。ただし、ターミナルデバイスではない場合、入力(出力が処理されるとき)の0x3バイト()がSIGINTをトリガし、コマンドを終了し、0x19バイト()がプロセスを停止することを意味します。 Disabled では、s が s に変更された理由を説明します。^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
inputにまたは文字がないと何も送信されません\r
。これは何も印刷されない理由を説明します。\n
printf foo | unbuffer -p cat
このラインエディタはラインサイズに制限があるので編集が可能です(私のシステムの4095(Linux)、ttyスピードの1/5FreeBSDの場合1)、次の結果になります。アンバッファリングすると、すべての文字がリングに変換されますか?:愚かなアプリケーション(たとえば
cat
、.Linuxでは、4094番目以降のすべての文字は無視されますが、許可されて行が送信されます。FreeBSDでは、38400/5文字以降の追加文字が拒否され(さらに)、 BEL結果が端末²に送信されます。これが2321 BEL(10001 - 38400/5)を取得する理由を説明します。\n
\n
擬似端末装置に対するEOF処理が難しい。 stdinでEOFが見つかった場合は、その
unbuffer
情報をcmd
。代わりに、その時点ですべてが解体され終了します(マンページにはこの制限が記載されています)。seq 10 | od -vtc
seq
od
od
unbuffer
独自の目的のために、組み込みraw -echo
の擬似端末をモードに設定し、ホスト端末装置(存在する場合)をそのままにしておくと便利です。ただし、expect
この動作モードは実際にはサポートされておらず、そのためには設計されていません。
今、unbuffer
stdoutのバッファリングを解除することについては、stdinとstderrに触れる理由はありません。
実際には、次の方法でこの問題を解決できます。
unbuffer() {
command unbuffer sh -c 4<&0 5>&2 '
exec <&4 4<&- 2>&5 5>&- "$@"' sh "$@"
}
これは元のstdinとstderrを復元するために使用されますsh
(fds 4と5を介して呼び出しシェルによって渡されます; fd 3はexpect
内部的に明示的に使用されているように使用されません)。
それから:
$ 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
pseudo-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&
バックグラウンド操作は最終的に端末で読み取られ、混乱を招きます。
1上限は65536です。スピード疑似端末の場合は関係ありませんが、広告が必要であり、テストしたFreeBSDシステムでは基本的に38400であることがわかりました。速度は制御端末の速度からコピーされるため、そのバッファを拡大するためにexpect
呼び出す前にstty speed 115200
(MAX AFAICT)を実行できます。unbuffer
しかし、まだ10,000文字の全行を取得できない可能性があります。それはドライバコードに記載されている。最初の呼び出しで要求されたバイト数unbuffer -p cat
であるため、4096バイトのみが返され、ttyドライバは入力ラインから同じ数のバイトを返すことがわかります。cat
read()
しかし残りは捨てた。(!)。に置き換えると、unbuffer -p dd bs=65536
行全体(最大115200/5バイト)が得られます。
²これらのBELをスクリプトで置き換えることで回避できますが、set stty_init "-echo"
データの取得には役立ちません。set stty_init "-echo -imaxbel"
unbuffer