Как программа узнает, подключен ли стандартный вывод к терминалу или каналу?


12

У меня проблемы с отладкой программы segfaulting, потому что мне нужен выходной поток перед segfault, но он теряется, если я передаю вывод в файл. Согласно этому ответу: /unix//a/17339/22615 , это связано с тем, что выходной буфер программы сбрасывается немедленно при подключении к терминалу, но только в определенных точках при подключении к каналу. Несколько вопросов здесь:

  • Как программа определяет, с чем связан ее стандартный вывод?

  • Как команда «script» производит такое же поведение, как когда программа пишет в терминал?

  • Можно ли этого достичь без команды сценария?


Ответы:


23

Сообщаем, указывает ли дескриптор файла на терминальное устройство

Программа может определить, связан ли файловый дескриптор с tty-устройством, используя isatty()стандартную функцию C (которая обычно выполняет безобидный tty-специфичный ioctl()системный вызов, который будет возвращаться с ошибкой, когда fd не указывает на tty-устройство) ,

[/ testУтилита может сделать это с -tоператором.

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

Отслеживание вызовов функций libc в системе GNU / Linux:

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

Отслеживание системных вызовов:

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

Говорить, если он указывает на трубу

Чтобы определить, связан ли fd с pipe / fifo, можно использовать fstat()системный вызов , который возвращает структуру, st_modeполе которой содержит тип и разрешения файла, открытого на этом fd. S_ISFIFO()Стандарт Си макро может быть использовано на этой st_modeобласти , чтобы определить , является ли ФД труба / FIFO.

Нет стандартной утилиты, которая может это сделать fstat(), но есть несколько несовместимых реализаций statкоманды, которая может это сделать. zsh«ы statвстроенный в stat -sf "$fd" +modeкоторый возвращает режим в виде строки представления, первый символ представляет тип ( pдля трубы). GNU statможет сделать то же самое 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и ksh93оболочки имеют встроенный поиск операторов , хотя:

$ 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использует псевдо-терминальную пару для захвата вывода программы, поэтому стандартный вывод программы (и stdin и stderr) будет псевдо-терминальным устройством.

Когда stdout подключен к терминальному устройству, обычно все еще существует некоторая буферизация, но она основана на линии. printf/ putsи co не будет ничего писать, пока не будет выведен символ новой строки. Для других типов файлов буферизация осуществляется блоками (по несколько килобайт).

Есть несколько вариантов , чтобы отключить буферизацию , которые обсуждаются в ряде Q & As здесь (поиск unbuffer или stdbuf , Может не перенаправление вывод покроя дает несколько подходов) , либо с помощью псевдо-терминала , как можно сделать socat/ script/ expect/ unbuffer( expectскрипт) / zsh«s zptyили путем введения кода в исполняемый файл , чтобы отключить буферизацию как это делается в GNU или файле FreeBSD stdbuf.


1
Отличный ответ, большое спасибо за это!
Mowwwalker

Другой подход, специфичный для Linux, заключается в том, чтобы обходить /procкаталог, и для каждого /proc/<integer>/каталога изучать/proc/<integer>/fd/ и находить файловый дескриптор с тем же номером инода в pipefs serverfault.com/q/48330/363611 Однако это полезно только в сценариях, когда нельзя использовать описанные системные вызовы. в ответе Стефана, и это скорее обходной путь, чем правильное решение ИМХО
Сергей Колодяжный

На BSD lseekбудет успешным на терминалах и других символьных устройствах, и просто переустановит / установит счетчик, который увеличивается при каждом успешном чтении (). Я не знаю, делает ли это их «доступными».
Мосви

@mowwwalker Если этот ответ решил вашу проблему, пожалуйста, найдите время и примите его , нажав на флажок слева. Это пометит вопрос как ответивший и выразит благодарность на сайтах Stack Exchange.
десерт
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.