程序如何知道 stdout 是否连接到终端或管道?

程序如何知道 stdout 是否连接到终端或管道?

我在调试段错误程序时遇到问题,因为段错误之前的输出正是我所需要的,但如果我将输出通过管道传输到文件,则会丢失。根据这个答案: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命令实现可以执行此操作。zshstat内置函数stat -sf "$fd" +mode以字符串表示形式返回模式,其第一个字符表示类型(p对于管道)。 GNUstat可以做同样的事stat -c %A - <&"$fd",但也必须stat -c %F - <&"$fd"报告类型独自的。对于 BSD statstat -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 所做的那样)。scriptexpectunbufferexpectzshzptystdbuf

相关内容