$ seq 10 | unbuffer -p od -vtc
0000000 1 \n 2 \n 3 \n 4 \n 5 \n 6 \n 7 \n 8 \n
Run Code Online (Sandbox Code Playgroud)
去了哪9
里10
?
$ printf '\r' | unbuffer -p od -An -w1 -vtc
\n
Run Code Online (Sandbox Code Playgroud)
为什么\r
改为\n
?
$ : | unbuffer -p printf '\n' | od -An -w1 -vtc
\r
\n
$ unbuffer -p printf '\n' | od -An -w1 -vtc
\r
\n
Run Code Online (Sandbox Code Playgroud)
搞什么?
$ printf foo | unbuffer -p cat
$
Run Code Online (Sandbox Code Playgroud)
为什么没有输出(并且延迟一秒)?
$ printf '\1\2\3foo bar\n' | unbuffer -p od -An -w1 -vtc
$
Run Code Online (Sandbox Code Playgroud)
为什么没有输出?
$ (printf '\23'; seq 10000) | unbuffer -p cat
Run Code Online (Sandbox Code Playgroud)
为什么它挂起并且没有输出?
$ unbuffer -p sleep 10
Run Code Online (Sandbox Code Playgroud)
为什么我看不到我输入的内容(为什么即使我sleep
没有阅读它也会被丢弃)?
顺便说一下,还有:
$ echo test | unbuffer -p grep foo && echo found foo
found foo
Run Code Online (Sandbox Code Playgroud)
为什么grep
找到foo
但没有打印包含它的行?
$ unbuffer -p ls /x 2> /dev/null
ls: cannot access '/x': No such file or directory
Run Code Online (Sandbox Code Playgroud)
为什么错误没有转到/dev/null
?
另请参阅取消缓冲将所有字符转换为响铃?
$ echo ${(l[10000][foo])} | unbuffer -p cat | wc -c
4095
Run Code Online (Sandbox Code Playgroud)
那是:
$ 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)
Run Code Online (Sandbox Code Playgroud)
在 Ubuntu 22.04 或 FreeBSD 12.4-RELEASE-p5 上也是如此(除了od
命令必须在那里进行调整,我得到 2321(那里的所有 BEL 字符)而不是上面的 4095)。
Sté*_*las 24
unbuffer
是一个工具,用于禁用某些命令在其输出未发送到终端设备时执行的缓冲。
当它们的输出发送到终端设备时,命令假设有一个实际用户正在主动查看输出,因此它们会在输出可用时立即发送它。好吧,不完全是,他们基于行发送,即一旦准备好输出就发送完整的行。
\n当它不发送到终端设备时,例如当 stdout 是常规文件或管道时,作为优化,它们会以块的形式发送它。这意味着更少的write()
s,并且在管道的情况下意味着另一端的读取器不需要经常被唤醒,这意味着更少的上下文切换。
然而,这意味着:
\ncmd | other-cmd\n
Run Code Online (Sandbox Code Playgroud)\n在终端中运行,其中other-cmd
有某种过滤/转换命令,other-cmd
\'s stdout 是行缓冲的,但cmd
\'s 是全缓冲的,这意味着交互式用户看不到cmd
(由other-cmd
)一旦可用,但延迟且大批量。
unbuffer cmd | other-cmd\n
Run Code Online (Sandbox Code Playgroud)\n有帮助,因为它恢复了基于行的缓冲,cmd
即使它的标准输出将进入管道。
为此,它cmd
从伪终端开始,并将来自该伪终端的内容转发到管道。因此cmd
认为它再次与用户交谈并进行行缓冲。
unbuffer
实际上是写在expect
. 它是源代码中的示例脚本expect
,通常包含在expect
操作系统提供的包中。
expect
是一种用于使用伪终端与终端应用程序执行自动交互的工具,因此该unbuffer
命令在expect
. 开玩笑地说,其手册页的BUGS部分有:手册页比程序长。事实上,该计划只是:unbuffer
#!/bin/sh\n# -*- tcl -*-\n# The next line is executed by /bin/sh, but not tcl \\\nexec tclsh8.6 "$0" ${1+"$@"}\n\npackage require Expect\n\n\n# -*- tcl -*-\n# Description: unbuffer stdout of a program\n# Author: Don Libes, NIST\n\nif {[string compare [lindex $argv 0] "-p"] == 0} {\n # pipeline\n set stty_init "-echo"\n eval [list spawn -noecho] [lrange $argv 1 end]\n close_on_eof -i $user_spawn_id 0\n interact {\n eof {\n # flush remaining output from child\n expect -timeout 1 -re .+\n return\n }\n }\n} else {\n set stty_init "-opost"\n set timeout -1\n eval [list spawn -noecho] $argv\n expect\n exit [lindex [wait] 3]\n}\n
Run Code Online (Sandbox Code Playgroud)\n正如您所看到的以及手册页所确认的,unbuffer
还支持一个-p
选项。
在 中unbuffer cmd
,伪终端不仅连接到 cmd 的 stdout,还连接到其 stdin 和 stderr(记住expect
是一个旨在与命令交互的工具):
$ tty; unbuffer readlink /proc/self/fd/{0..2}\n/dev/pts/14\n/dev/pts/15\n/dev/pts/15\n/dev/pts/15\n
Run Code Online (Sandbox Code Playgroud)\n这解释了为什么unbuffer ls /x 2> /dev/null
没有将错误发送到/dev/null
,stderr 与 stdout 合并。
现在,unbuffer
不从其自己的标准输入读取任何内容,也不向 的标准输入发送任何内容cmd
。
这意味着A | unbuffer cmd | B
行不通。
这就是-p
(for 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 字节 ( ) 都会停止流程。未被禁用解释了为什么\'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
,所以 withopost
禁用 解释了当 输出的换行符od
未转换为时,终端输出被破坏\\r\\n
时,终端输出被破坏。
cmd
内部伪终端仍然启用行编辑器,因此除非有来自输入的\\r
或字符,否则不会发送任何内容,这解释了为什么\\n
printf foo | unbuffer -p cat
,这解释了为什么不打印任何内容。
由于该行编辑器对可编辑行的大小有限制(我的系统(Linux)上为 4095, FreeBSD 上tty 速度 \xc2\xb9 的五分之一),因此您最终会在Unbuffer中遇到此类问题将所有字符转换为响铃?:当您尝试在哑应用程序(例如 )中在键盘上输入过长的行时,会发生同样的情况cat
。在 Linux 上,第 4094个之后的所有字符都将被忽略,但\\n
会被接受并提交该行;在 FreeBSD 上,输入 38400/5 个字符后,任何多余的字符都会被拒绝(甚至\\n
),并导致 BEL 被发送到终端\xc2\xb2。这解释了为什么你在那里得到 2321 BEL (10001 - 38400/5)。
伪终端设备的 EOF 处理很笨重。当在标准输入上看到 EOF 时unbuffer
,它无法将该信息转发到cmd
. 因此seq 10 | od -vtc
,在seq
终止后,od
仍在等待来自伪终端的更多输入,而这些输入永远不会到来。相反,到那时,一切都被拆除并被od
杀死(手册页确实提到了这个限制)。
unbuffer
就其自身目的而言,如果将嵌入式伪终端置于raw -echo
模式下并保留主机终端设备(如果有),效果会更好。然而expect
并不真正支持这种操作模式,它不是为此设计的。
现在,如果unbuffer
是关于取消缓冲标准输出,那么它没有理由接触标准输入和标准错误。
我们实际上可以通过以下方式解决这个问题:
\nunbuffer() {\n command unbuffer sh -c 4<&0 5>&2 \'\n exec <&4 4<&- 2>&5 5>&- "$@"\' sh "$@"\n}\n
Run Code Online (Sandbox Code Playgroud)\n它用于sh
恢复原始的 stdin 和 stderr (由调用 shell 通过 fds 4 和 5 传递;不使用 fd 3 作为expect
在内部显式使用该 fd 3 一样)。
然后:
\n$ echo test | unbuffer readlink /proc/self/fd/{0..2} 2> /dev/null | cat\npipe:[184479]\n/dev/pts/16\n/dev/null\n
Run Code Online (Sandbox Code Playgroud)\n只有 stdout 进入伪终端以进行无缓冲。
\n所有其他问题都消失了:
\n$ unbuffer ls /x 2> /dev/null\n$ printf \'\\r\' | unbuffer od -An -w1 -vtc\n \\r\n$ : | unbuffer printf \'\\n\' | od -An -w1 -vtc\n \\n\n$ unbuffer printf \'\\n\' | od -An -w1 -vtc\n \\n\n$ printf foo | unbuffer cat\nfoo\n$ printf \'\\1\\2\\3foo bar\\n\' | unbuffer od -An -w1 -vtc\n 001\n 002\n 003\n f\n o\n o\n\n b\n a\n r\n \\n\n$ (printf \'\\23\'; seq 10000) | unbuffer cat -vte | head\n^S1$\n2$\n3$\n4$\n5$\n6$\n7$\n8$\n9$\n10$\n$ unbuffer sleep 10\nI see what I type\n$ I see what I type\nzsh: command not found: I\n$ echo test | unbuffer grep foo || echo not found\nnot found\n$ echo ${(l[10000][foo])} | unbuffer cat | wc -c\n10001\n
Run Code Online (Sandbox Code Playgroud)\nexpect
当您需要的只是使 stdout 时,安装(需要 TCL 解释器)似乎有点矫枉过正cmd
另外,当您需要的只是通过伪终端进行标准输出
socat
也可以这样做:
$ echo test | socat -u system:\'readlink /proc/self/fd/[0-2]; wc -c\',pty,raw - 2> /dev/null | cat\npipe:[187759]\n/dev/pts/17\n/dev/null\n5\n
Run Code Online (Sandbox Code Playgroud)\n(它记录失败退出状态,但不会传播命令的退出状态)。
\nshellzsh
甚至内置了对伪 ttys 的支持,并且unbuffer
可以轻松地编写一个函数:
zmodload zsh/zpty\nzmodload zsh/zselect\nunbuffer() {\n {\n return "$(\n exec 6>&1 >&5 5>&-\n # here fds go:\n # 0,3: orig stdin\n # 1: orig stdout\n # 2,4: orig stderr\n # 5: closed\n # 6: to return argument\n zpty -b unbuffer \'\n stty raw\n exec <&3 3<&- 2>&4 4>&-\n # here fds go:\n # 0: orig stdin\n # 1: pseudo unbuffering tty\n # 2: orig stderr\n # 3,4,5: closed\n # 6: to return argument\n "$@" 6>&-\n echo "$?" >&6 \n \'\n fd=$REPLY\n until\n zselect -r $fd\n zpty -r unbuffer\n (( $? == 2 ))\n do\n continue\n done\n )"\n } 3<&0 4>&2 5>&1\n}\n
Run Code Online (Sandbox Code Playgroud)\n请注意,所有这些最终都会在新终端中运行,除了新会话中的socat
方法(除非您使用ctty
和选项)。setid
因此,现在如果这些“fixed”unbuffer
在主机终端会话的后台启动,则cmd
不会停止从主机终端读取数据。例如unbuffer cat&
最终会从您的终端读取后台作业,从而造成严重破坏。
\xc2\xb9 上限为 65536。伪终端的速度无关紧要,但必须有一个广告,我发现在我测试的 FreeBSD 系统上默认情况下它是 38400。由于速度是从控制终端的速度复制的expect
,因此可以在调用之前执行stty speed 115200
(最大值AFAICT)unbuffer
以扩大该缓冲区。但您可能会发现您仍然没有获得完整的 10000 字符大行。驱动程序代码中对此进行了解释。您会发现unbuffer -p cat
仅返回 4096 字节,因为这与cat
第一次read()
调用中请求的字节数一样多,并且 tty 驱动程序从输入行返回了同样多的字节数,但丢弃了其余部分(!)。如果替换为unbuffer -p dd bs=65536
,您将获得完整的行(最多 115200/5 字节)。
set stty_init "-echo"
\xc2\xb2 您可以通过set stty_init "-echo -imaxbel"
在脚本中替换为来避免这些 BEL unbuffer
,但这不会帮助您获取数据。