Як програма знає, чи stdout підключений до терміналу чи труби?


12

У мене виникають проблеми з налагодженням програми segfaulting, оскільки вихід, який знаходиться безпосередньо перед segfault, - це те, що мені потрібно, але це втрачається, якщо я пересилаю висновок у файл. Відповідно до цієї відповіді: /unix//a/17339/22615 , це пояснюється тим, що вихідний буфер програми зливається негайно при підключенні до терміналу, але лише в певних точках при підключенні до труби. Кілька питань тут:

  • Як програма визначає, до чого пов'язаний її stdout?

  • Як команда "скрипт" виробляє таку поведінку, що і тоді, коли програма записує в термінал?

  • Чи можна цього досягти без команди скрипта?


Питання, пов’язане з цим, є unix.stackexchange.com/q/513926/5132 .
JdeBP

Відповіді:


23

Скажіть, чи дескриптор файлу вказує на термінальний пристрій

Програма може визначити, чи пов'язаний дескриптор файлу з пристроєм tty за допомогою isatty()стандартної функції C (яка, як правило, під ним виконує нешкідливий ioctl()системний виклик, який відповідає tty, який би повертався з помилкою, коли 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's 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' zptyабо шляхом введення коду у виконуваний файл, щоб відключити буферизацію, як це зроблено GNU або FreeBSD stdbuf.


1
Дивовижна відповідь, дуже дякую за це!
mowwwalker

Інший підхід, орієнтований на Linux, полягає в тому, щоб перейти до /procкаталогу та для кожного /proc/<integer>/каталогу вивчити /proc/<integer>/fd/та знайти дескриптор файлу, який має однаковий номер inode в pipefs сервері defaultfault.com/q/48330/363611 Однак, це корисно лише в сценаріях, коли не можна використовувати описані систематичні виклики. у відповідь Стефана, і є скоріше рішенням, ніж правильним рішенням ІМХО
Сергій Колодяжний

На BSD lseekбуде досягти успіху на терміналах та інших пристроях символів, і просто повторно встановити лічильник, який збільшується при кожному успішному прочитанні (). Я не знаю, чи це робить їх "шукаючими".
mosvy

@mowwwalker Якщо ця відповідь вирішила вашу проблему, будь-ласка, знайдіть хвилинку та прийміть її , натиснувши на галочку зліва. Це означатиме відповідь на питання і спосіб висловлення подяки на сайтах Stack Exchange.
десерт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.