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


12

我在调试segfaulting程序时遇到了麻烦,因为我需要的是segfault之前的输出,但是如果将输出管道传输到文件中,则会丢失此信息。根据以下答案:https : //unix.stackexchange.com/a/17339/22615,这是因为程序的输出缓冲区在连接到终端时会立即刷新,但在连接到管道时才在特定点刷新。这里有几个问题:

  • 程序如何确定其stdout连接到什么?

  • “脚本”命令如何产生与程序写入终端时相同的行为?

  • 如果没有script命令,可以实现吗?


一个相关的问题是unix.stackexchange.com/q/513926/5132
JdeBP

Answers:


23

告诉文件描述符是否指向终端设备

程序可以使用isatty()标准的C函数来判断文件描述符是否与tty设备相关联(通常在下面执行无害的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用于管道)。GNU stat可以使用进行相同的操作stat -c %A - <&"$fd",但是还必须单独stat -c %F - <&"$fd"报告类型。使用BSD statstat -f %St <&"$fd"stat -f %HT <&"$fd"

告诉它是否可寻

应用程序通常并不关心stdout是否是管道。他们可能会在乎它是否可取(尽管通常不决定是否缓冲)。

为了测试fd是否可搜索(管道,套接字,tty设备不可搜索,常规文件以及大多数块设备通常都可以),可以尝试使用偏移量为0(无害)的相对lseek()系统调用dd是一个标准实用程序,是lseek()与之的接口,但不能用于该测试,因为lseek()如果您要求偏移量为0 ,则实现将根本不会调用该实用程序。

zshksh93壳有内置寻求运营商,但:

$ 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)将成为伪终端设备。

当标准输出到终端设备时,通常仍然会有一些缓冲,但是它是基于行的。printf/ puts和co将不会写入任何内容,直到要输出换行符为止。对于其他类型的文件,按块(几千字节)缓冲。

有几种选择来禁用其以数Q&如这里所讨论的缓冲(搜索非缓冲stdbuf不重定向切输出可通过使用伪终端作为可以这样做给出了几个方法)或者socat/ script/ expect/ unbuffer(一个expect脚本)/ zshzpty或通过在可执行代码注入禁用缓冲由GNU的或FreeBSD的完成stdbuf


1
很棒的答案,非常感谢您!
mowwwalker '19

另一种特定于Linux的方法是遍历/proc目录,并针对每个/proc/<integer>/目录查找/proc/<integer>/fd/并在pipefs serverfault.com/q/48330/363611中查找具有相同inode编号的文件描述符, 但是,这仅在脚本中不能使用所描述的syscall时才有用在斯蒂芬(Stephane)的答案中,它不仅仅是适当的解决方案,而是一种解决方法。恕我直言
Sergiy Kolodyazhnyy

在BSD上,lseek将在终端和其他字符设备上成功,并且只需重新/设置一个计数器,该计数器将在每次成功的read()上增加。我不知道这是否使他们“可以寻找”。
mosvy

@mowwwalker如果此答案解决了您的问题,请花一点时间并单击左侧的复选标记以接受。这会将问题标记为已回答,并且是在Stack Exchange网站上表达感谢的方式。
甜点,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.