¿Cómo sabe un programa si stdout está conectado a un terminal o una tubería?


12

Tengo problemas para depurar un programa de segfaulting porque lo que necesito es la salida justo antes de la segfault, pero esto se pierde si estoy canalizando la salida a un archivo. De acuerdo con esta respuesta: /unix//a/17339/22615 , esto se debe a que el búfer de salida del programa se lava inmediatamente cuando se conecta a un terminal, pero solo en ciertos puntos cuando se conecta a una tubería. Algunas preguntas aquí:

  • ¿Cómo determina un programa a qué está conectado su stdout?

  • ¿Cómo produce el comando "script" el mismo comportamiento que cuando el programa escribe en un terminal?

  • ¿Se puede lograr esto sin el comando de script?


Una pregunta relacionada es unix.stackexchange.com/q/513926/5132 .
JdeBP

Respuestas:


23

Indicar si un descriptor de archivo apunta a un dispositivo terminal

Un programa puede determinar si un descriptor de archivo está asociado con un dispositivo tty mediante el uso de la isatty()función C estándar (que generalmente hace una ioctl()llamada de sistema específica de tty inocua que volvería con un error cuando el fd no apunta a un dispositivo tty) .

La utilidad [/ testpuede hacerlo con su -toperador.

if [ -t 1 ]; then
  echo stdout is open to a terminal
fi

Seguimiento de llamadas de función libc en un sistema GNU / Linux:

$ ltrace [ -t 1 ] | cat
[...]
isatty(1)                                      = 0
[...]

Sistema de rastreo de llamadas:

$ strace [ -t 1 ] | cat
[...]
ioctl(1, TCGETS, 0x7fffd9fb3010)        = -1 ENOTTY (Inappropriate ioctl for device)
[...]

Decir si apunta a una tubería

Para determinar si un fd está asociado con un pipe / fifo, se puede usar la fstat()llamada al sistema , que devuelve una estructura cuyo st_modecampo contiene el tipo y los permisos del archivo abierto en ese fd. La S_ISFIFO()macro C estándar se puede usar en ese st_modecampo para determinar si el fd es una tubería / quince.

No hay una utilidad estándar que pueda hacer un fstat(), pero hay varias implementaciones incompatibles de un statcomando que puede hacerlo. zsh's statincorporado con el stat -sf "$fd" +modeque devuelve el modo como una representación de cadena cuyo primer carácter representa el tipo ( ppara tubería). GNU statpuede hacer lo mismo con stat -c %A - <&"$fd", pero también tiene stat -c %F - <&"$fd"que informar el tipo solo. Con BSD stat: stat -f %St <&"$fd"o stat -f %HT <&"$fd".

Diciendo si es buscable

Sin embargo, a las aplicaciones generalmente no les importa si stdout es una tubería. Puede que les importe que sea buscable (aunque generalmente no para decidir si usar un búfer o no).

Para probar si un fd es buscable (las tuberías, los enchufes, los dispositivos tty no son buscables, los archivos normales y la mayoría de los dispositivos de bloque generalmente lo son), se puede intentar una lseek()llamada relativa al sistema con un desplazamiento de 0 (tan inocuo). ddes una utilidad estándar que es una interfaz lseek()pero no se puede usar para esa prueba, ya que las implementaciones no llamarían lseek()en absoluto si solicita un desplazamiento de 0.

Sin embargo, los proyectiles zshy ksh93tienen operadores de búsqueda incorporados:

$ 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

Deshabilitar el almacenamiento en búfer

El scriptcomando utiliza un par pseudo-terminal para capturar la salida de un programa, por lo que stdout (y stdin y stderr) del programa será un dispositivo pseudo-terminal.

Cuando la salida estándar es para un dispositivo terminal, generalmente todavía hay algo de almacenamiento en búfer, pero se basa en líneas. printf/ putsy co no escribirán nada hasta que salga un carácter de nueva línea. Para otros tipos de archivos, el almacenamiento en búfer es por bloques (de unos pocos kilobytes).

Hay varias opciones para desactivar el almacenamiento en búfer que se discuten en una serie de Q & A aquí (búsqueda de unbuffer o stdbuf , No se puede redirigir la salida de corte da algunos enfoques), ya sea mediante el uso de una pseudo-terminal como se puede hacer por socat/ script/ expect/ unbuffer(un expectscript) / zsh's zptyo inyectando código en el ejecutable para deshabilitar el almacenamiento en búfer como lo hacen GNU o FreeBSD stdbuf.


1
Impresionante respuesta, muchas gracias por esto!
mowwwalker

Otro enfoque específico de Linux es recorrer el /procdirectorio y para cada /proc/<integer>/directorio buscar /proc/<integer>/fd/y encontrar un descriptor de archivo que tenga el mismo número de inodo en pipefs serverfault.com/q/48330/363611 Sin embargo, eso solo es útil en los scripts cuando no se pueden usar los syscalls descritos en la respuesta de Stephane, y es más una solución que una solución adecuada en mi humilde opinión
Sergiy Kolodyazhnyy

En BSD, lseektendrá éxito en terminales y otros dispositivos de caracteres, y simplemente re / establecerá un contador que se incrementa en cada lectura exitosa (). No sé si esto los hace "buscables".
mosvy

@mowwwalker Si esta respuesta resuelve su problema, por favor tome un momento y aceptarla haciendo clic en la marca de verificación a la izquierda. Eso marcará la pregunta como respondida y es la forma en que se agradece en los sitios de Stack Exchange.
postre
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.