プログラムは、stdoutがターミナルまたはパイプに接続されているかどうかをどのように知るのですか?


12

セグメンテーション違反の直前の出力が必要なため、セグメンテーション違反プログラムのデバッグに問題がありますが、出力をファイルにパイピングするとこれは失われます。この回答:https : //unix.stackexchange.com/a/17339/22615によると、これは、プログラムの出力バッファーが端末に接続されたときにすぐにフラッシュしますが、パイプに接続されたときは特定のポイントでのみです。ここにいくつか質問があります:

  • プログラムは、標準出力の接続先をどのように判断しますか?

  • 「スクリプト」コマンドは、プログラムが端末に書き込むときと同じ動作をどのように生成しますか?

  • スクリプトコマンドなしでこれを実現できますか?


関連する質問はunix.stackexchange.com/q/513926/5132です。
JdeBP

回答:


23

ファイル記述子が端末デバイスを指しているかどうかを伝える

プログラムは、isatty()標準のC関数を使用して、ファイル記述子がttyデバイスに関連付けられているかどうかを確認できます(通常はioctl()、fdがttyデバイスを指していないときにエラーを返す無害な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_modeFDが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デバイスはシーク不可、通常のファイル、およびほとんどのブロックデバイスは一般にそうです)、相対的な lseek() 0のシステムコール(無害)ます。ddインターフェースである標準ユーティリティですが、オフセット0を要求するlseek()と実装はまったく呼び出さlseek()れないため、そのテストには使用できません。

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コマンドは、擬似端末ペアを使用してプログラムの出力をキャプチャするため、プログラムのstdout(およびstdinとstderr)は擬似端末デバイスになります。

stdoutが端末デバイスに対するものである場合、一般にまだある程度のバッファリングがありますが、それは行ベースです。 printf/ putsおよびcoは、改行文字が出力されるまで何も書き込みません。他の種類のファイルの場合、バッファリングはブロック単位(数キロバイト)です。

ここでQ&Asの数で議論されているバッファリングを無効にするには、いくつかのオプションがあります(を検索バッファー解除またはstdbufは缶リダイレクトないカット出力は、いくつかのアプローチを与える)ことによって行うことができるよう疑似端末を使用して、どちらかsocat/ script/ expect/ unbufferexpectスクリプト)/ zshさんzptyまたはGNUの又はFreeBSDのにより行われるようにバッファリングを無効にするために実行可能のコードを注入することによりstdbuf


1
素晴らしい回答、ありがとうございました!
mowwwalker

もう、Linux固有のアプローチが横断するためにある/procディレクトリを、それぞれのため/proc/<integer>/に、ディレクトリルック/proc/<integer>/fd/と同じinode番号を持つファイルディスクリプタ見つけるpipefs serverfault.com/q/48330/363611 1が説明システムコールを使用できない場合、スクリプトでのみ有用であるしかしを、ステファンの答えであり、より多くの回避策の適切な解決策の私見より
Sergiy Kolodyazhnyy

BSDでは、lseek端末およびその他のキャラクターデバイスで成功し、read()が成功するたびに増加するカウンターを単純に再設定します。これがそれらを「シーク可能」にするかどうかはわかりません。
mosvy

@mowwwalkerこの回答で問題が解決した場合は、左のチェックマークをクリックして、しばらくしてから受け入れてください。これにより、質問に回答済みのマークが付けられ、Stack Exchangeサイトで感謝が表明されます。
デザート
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.