標準出力が端末に送信されている場合、または別のプロセスにパイプされている場合、シェルスクリプト内からどのように検出できますか?
適切な例:エスケープコードを追加して出力を色付けしたいのですが、ls --color
のように、インタラクティブに実行するときだけで、パイプでは実行しません。
純粋なPOSIXシェルでは、
if [ -t 1 ] ; then echo terminal; else echo "not a terminal"; fi
出力は端末に送信されるため、「端末」を返しますが、
(if [ -t 1 ] ; then echo terminal; else echo "not a terminal"; fi) | cat
括弧の出力はcat
にパイプされるため、「not a terminal」を返します。
-t
フラグは、manページで次のように説明されています
-t fdファイル記述子fdが開いており、端末を参照している場合は真です。
...ここで、fd
は、通常のファイル記述子の割り当てのいずれかです。
0: stdin
1: stdout
2: stderr
主にssh
などのプログラムが原因で、STDIN、STDOUT、またはSTDERRがスクリプトとの間でパイプされているかどうかを判断するfoolproofの方法はありません。
たとえば、次のbashソリューションは、対話型シェルで正しく機能します。
[[ -t 1 ]] && \
echo 'STDOUT is attached to TTY'
[[ -p /dev/stdout ]] && \
echo 'STDOUT is attached to a pipe'
[[ ! -t 1 && ! -p /dev/stdout ]] && \
echo 'STDOUT is attached to a redirection'
ただし、このコマンドを非TTY ssh
コマンドとして実行すると、STDストリームalwaysはパイプされているように見えます。これを実証するために、STDINを使用すると簡単です。
# CORRECT: Forced-tty mode correctly reports '1', which represents
# no pipe.
ssh -t localhost '[[ -p /dev/stdin ]]; echo ${?}'
# CORRECT: Issuing a piped command in forced-tty mode correctly
# reports '0', which represents a pipe.
ssh -t localhost 'echo hi | [[ -p /dev/stdin ]]; echo ${?}'
# INCORRECT: Non-tty mode reports '0', which represents a pipe,
# even though one isn't specified here.
ssh -T localhost '[[ -p /dev/stdin ]]; echo ${?}'
これはかなり重要です。なぜなら、bashスクリプトが非tty ssh
コマンドがパイプされているかどうかを判断する方法がないことを意味するからです。この不幸な振る舞いは、ssh
の最近のバージョンが非TTY STDIOのパイプの使用を開始したときに導入されたことに注意してください。以前のバージョンではソケットを使用していましたが、[[ -S ]]
を使用してbash内と区別できます。
通常、この制限は、cat
などのコンパイルされたユーティリティに似た動作を持つbashスクリプトを作成するときに問題を引き起こします。たとえば、cat
は、さまざまな入力ソースを同時に処理する際に次の柔軟な動作を可能にし、非TTYまたは強制TTY ssh
が使用されているかどうかに関係なく、パイプ入力を受信しているかどうかを判断するのに十分スマートです:
ssh -t localhost 'echo piped | cat - <( echo substituted )'
ssh -T localhost 'echo piped | cat - <( echo substituted )'
パイプが関与しているかどうかを確実に判断できる場合にのみ、そのようなことができます。そうしないと、パイプまたはリダイレクトからの入力がないときにSTDINを読み取るコマンドを実行すると、スクリプトがハングしてSTDIN入力を待機します。
この問題を解決するために、問題を解決できないいくつかの手法を検討しました。
stat
を使用[[ "${-}" =~ 'i' ]]
を介してインタラクティブモードを調べるtty
およびtty -s
を介してttyステータスを調べる[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]
を介してssh
ステータスを調べる/proc
仮想ファイルシステムをサポートするOSを使用している場合は、STDIOのシンボリックリンクをたどってパイプが使用されているかどうかを判断することができます。ただし、/proc
はクロスプラットフォームのPOSIX互換ソリューションではありません。
この問題を解決するのは非常に興味深いので、動作する可能性のある他の手法、できればLinuxとBSDの両方で動作するPOSIXベースのソリューションを考えている場合はお知らせください。
コマンドtest
(ビルトインbash
)には、ファイル記述子がttyであるかどうかを確認するオプションがあります。
if [ -t 1 ]; then
# stdout is a tty
fi
「man test
」または「man bash
」を参照して、「-t
」を検索してください
使用しているシェルについては言及しませんが、Bashではこれを行うことができます。
#!/bin/bash
if [[ -t 1 ]]; then
# stdout is a terminal
else
# stdout is not a terminal
fi
Solarisでは、Dejay Claytonからの提案のほとんどが機能します。 -pは必要に応じて応答しません。
bash_redir_test.shは次のようになります。
[[ -t 1 ]] && \
echo 'STDOUT is attached to TTY'
[[ -p /dev/stdout ]] && \
echo 'STDOUT is attached to a pipe'
[[ ! -t 1 && ! -p /dev/stdout ]] && \
echo 'STDOUT is attached to a redirection'
Linuxでは、うまく機能します。
:$ ./bash_redir_test.sh
STDOUT is attached to TTY
:$ ./bash_redir_test.sh | xargs echo
STDOUT is attached to a pipe
:$ rm bash_redir_test.log
:$ ./bash_redir_test.sh >> bash_redir_test.log
:$ tail bash_redir_test.log
STDOUT is attached to a redirection
Solarisの場合:
:# ./bash_redir_test.sh
STDOUT is attached to TTY
:# ./bash_redir_test.sh | xargs echo
STDOUT is attached to a redirection
:# rm bash_redir_test.log
bash_redir_test.log: No such file or directory
:# ./bash_redir_test.sh >> bash_redir_test.log
:# tail bash_redir_test.log
STDOUT is attached to a redirection
:#
次のコード(linux bash 4.4でのみテスト済み)移植性も推奨もありませんですが、完全を期すために以下に示します。
ls /proc/$$/fdinfo/* >/dev/null 2>&1 || grep -q 'flags: 00$' /proc/$$/fdinfo/0 && echo "pipe detected"
理由はわかりませんが、bash関数にSTDINがパイプされると、ファイル記述子「3」が何らかの形で作成されるようです。
それが役に立てば幸い、