我在调试段错误程序时遇到问题,因为段错误之前的输出正是我所需要的,但如果我将输出通过管道传输到文件,则会丢失。根据这个答案:https://unix.stackexchange.com/a/17339/22615,这是因为程序的输出缓冲区在连接到终端时立即刷新,但仅在连接到管道时在某些点刷新。这里有几个问题:
程序如何确定其标准输出连接到什么?
“script”命令如何产生与程序写入终端时相同的行为?
不用script命令可以实现吗?
答案1
判断文件描述符是否指向终端设备
程序可以通过使用以下命令判断文件描述符是否与 tty 设备关联isatty()
标准 C 函数(通常在下面执行无害的特定于 tty 的ioctl()
系统调用,当 fd 不指向 tty 设备时,该系统调用将返回错误)。
[
/实用test
程序可以通过其运算符来完成此-t
操作。
if [ -t 1 ]; then
echo stdout is open to a terminal
fi
跟踪 GNU/Linux 系统上的 libc 函数调用:
$ ltrace [ -t 1 ] | cat
[...]
isatty(1) = 0
[...]
跟踪系统调用:
$ strace [ -t 1 ] | cat
[...]
ioctl(1, TCGETS, 0x7fffd9fb3010) = -1 ENOTTY (Inappropriate ioctl for device)
[...]
判断它是否指向管道
要确定 fd 是否与管道/fifo 关联,可以使用fstat()
系统调用,它返回一个结构体,其st_mode
字段包含在该 fd 上打开的文件的类型和权限。这S_ISFIFO()
标准C宏可以在该字段上使用来st_mode
确定 fd 是否是管道/fifo。
没有可以执行此操作的标准实用程序fstat()
,但有几个不兼容的stat
命令实现可以执行此操作。zsh
的stat
内置函数stat -sf "$fd" +mode
以字符串表示形式返回模式,其第一个字符表示类型(p
对于管道)。 GNUstat
可以做同样的事stat -c %A - <&"$fd"
,但也必须stat -c %F - <&"$fd"
报告类型独自的。对于 BSD stat
:stat -f %St <&"$fd"
或stat -f %HT <&"$fd"
.
判断是否可搜索
应用程序通常不关心 stdout 是否是管道。他们可能关心它是否可查找(尽管通常不决定是否缓冲)。
要测试 fd 是否可查找(管道、套接字、tty 设备不可查找,常规文件和大多数块设备通常是可查找的),可以尝试相对lseek()
系统调用偏移量为 0(所以无害)。dd
是一个标准实用程序,它是一个接口,lseek()
但它不能用于该测试,因为lseek()
如果您要求偏移量为 0,则实现根本不会调用。
zsh
和shellksh93
有内置的查找运算符:
$ strace -e lseek ksh -c ': 1>#((CUR))' | cat
lseek(1, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek)
ksh: 1: not seekable
$ strace -e lseek zsh -c 'zmodload zsh/system; sysseek -w current -u 1 0 || syserror'
lseek(1, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek)
Illegal seek
禁用缓冲
该script
命令使用伪终端对来捕获程序的输出,因此程序的 stdout(以及 stdin 和 stderr)将是伪终端设备。
当 stdout 是终端设备时,通常仍会有一些缓冲,但它是基于行的。printf
/puts
和 co 不会写入任何内容,直到要输出换行符。对于其他类型的文件,缓冲是按块(几千字节)进行的。
有几个选项可以禁用缓冲,这些选项在此处的许多问答中进行了讨论(搜索解缓冲或者标准缓冲区,无法重定向剪切输出给出了一些方法)要么使用伪终端(如// socat
(脚本)/ ),要么通过在可执行文件中注入代码来禁用缓冲(如 GNU 或 FreeBSD 所做的那样)。script
expect
unbuffer
expect
zsh
zpty
stdbuf