Bagaimana suatu program tahu jika stdout terhubung ke terminal atau pipa?


12

Saya mengalami kesulitan men-debug program segfault karena ouput tepat sebelum segfault adalah yang saya butuhkan, tetapi ini hilang jika saya memipipkan output ke file. Menurut jawaban ini: /unix//a/17339/22615 , ini karena buffer output dari program memerah segera ketika terhubung ke terminal tetapi hanya pada titik-titik tertentu ketika terhubung ke pipa. Beberapa pertanyaan di sini:

  • Bagaimana cara program menentukan apa stdout yang terhubung?

  • Bagaimana perintah "script" menghasilkan perilaku yang sama seperti ketika program menulis ke terminal?

  • Bisakah ini dicapai tanpa perintah skrip?


Pertanyaan terkait adalah unix.stackexchange.com/q/513926/5132 .
JdeBP

Jawaban:


23

Memberitahu jika deskriptor file menunjuk ke perangkat terminal

Suatu program dapat mengetahui apakah deskriptor file dikaitkan dengan perangkat tty dengan menggunakan isatty()fungsi C standar (yang umumnya di bawahnya melakukan ioctl()panggilan sistem tty khusus yang tidak berbahaya yang akan kembali dengan kesalahan ketika fd tidak menunjuk ke perangkat tty) .

The [/ testutilitas dapat melakukannya dengan nya -toperator.

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

Melacak panggilan fungsi libc pada sistem GNU / Linux:

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

Melacak panggilan sistem:

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

Memberitahu jika menunjuk ke pipa

Untuk menentukan apakah fd dikaitkan dengan pipa / fifo, seseorang dapat menggunakan fstat()pemanggilan sistem , yang mengembalikan struktur yang st_modebidangnya berisi jenis dan izin file yang dibuka pada fd itu. The S_ISFIFO()standar C makro dapat digunakan pada yang st_modelapangan untuk menentukan apakah fd adalah pipa / fifo.

Tidak ada utilitas standar yang dapat melakukan fstat(), tetapi ada beberapa implementasi statperintah yang tidak kompatibel yang dapat melakukannya. zsh's statbuiltin dengan stat -sf "$fd" +modeyang mengembalikan mode sebagai representasi string yang karakter pertama mewakili jenis ( puntuk pipa). GNU statdapat melakukan hal yang sama dengan stat -c %A - <&"$fd", tetapi juga harus stat -c %F - <&"$fd"melaporkan jenisnya sendiri. Dengan BSD stat: stat -f %St <&"$fd"atau stat -f %HT <&"$fd".

Memberitahu jika itu bisa dicari

Aplikasi umumnya tidak peduli jika stdout adalah pipa. Mereka mungkin peduli bahwa itu dapat dicari (meskipun umumnya tidak memutuskan apakah akan buffer atau tidak).

Untuk menguji apakah fd dapat dicari (pipa, soket, perangkat tty tidak dapat dicari, file biasa dan sebagian besar perangkat blok umumnya), seseorang dapat mencoba lseek()panggilan sistem relatif dengan offset 0 (sangat tidak berbahaya). ddadalah utilitas standar yang merupakan antarmuka lseek()tetapi tidak dapat digunakan untuk pengujian itu, karena implementasi tidak akan memanggil lseek()sama sekali jika Anda meminta offset 0.

The zshdan ksh93kerang memiliki builtin mencari operator meskipun:

$ 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

Menonaktifkan buffering

The scriptperintah menggunakan sepasang pseudo-terminal untuk menangkap output dari sebuah program, sehingga stdout program (dan stdin dan stderr) akan menjadi perangkat pseudo-terminal.

Ketika stdout ke perangkat terminal, umumnya masih ada beberapa buffering, tetapi berbasis garis. printf/ putsdan co tidak akan menulis apa pun sampai karakter baris baru akan dihasilkan. Untuk jenis file lain, buffering adalah dengan blok (beberapa kilo byte).

Ada beberapa opsi untuk menonaktifkan buffering yang dibahas dalam sejumlah tanya jawab di sini (mencari unbuffer atau stdbuf , Tidak dapat mengalihkan reduksi output memberikan beberapa pendekatan) baik dengan menggunakan pseudo-terminal seperti yang dapat dilakukan oleh socat/ script/ expect/ unbuffer(sebuah expectscript) / zsh's zptyatau dengan menyuntikkan kode dalam executable untuk menonaktifkan buffering seperti yang dilakukan oleh GNU atau FreeBSD stdbuf.


1
Jawaban yang luar biasa, terima kasih banyak untuk ini!
mowwwalker

Lain, pendekatan khusus Linux adalah untuk menelusuri /procdirektori dan untuk setiap /proc/<integer>/direktori melihat ke dalam /proc/<integer>/fd/dan menemukan file descriptor yang memiliki nomor inode yang sama di pipefs serverfault.com/q/48330/363611 Namun, itu hanya berguna dalam skrip ketika seseorang tidak dapat menggunakan syscalls yang dijelaskan dalam jawaban Stephane, dan lebih merupakan solusi daripada solusi IMHO yang tepat
Sergiy Kolodyazhnyy

Pada BSD, lseekakan berhasil pada terminal dan perangkat karakter lainnya, dan cukup mengatur ulang / menetapkan penghitung yang meningkat pada setiap pembacaan yang berhasil (). Saya tidak tahu apakah ini membuat mereka "dapat dicari".
Mosvy

@mowwwalker Jika jawaban ini menyelesaikan masalah Anda, mohon luangkan waktu dan terima dengan mengklik tanda centang di sebelah kiri. Itu akan menandai pertanyaan sebagai dijawab dan cara terima kasih diungkapkan di situs Stack Exchange.
hidangan penutup
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.