標準出力が端末に送信されているか、別のプロセスにパイプされているかをシェルスクリプト内から検出するにはどうすればよいですか?
適切な例: 出力を色付けするためにエスケープ コードを追加したいのですが、対話的に実行する場合のみであり、パイプされた場合はそうで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
。
-t
フラグは、man ページで次のように説明されています。
-t fd ファイル記述子 fd が開いていて、端末を参照している場合は真です。
...fd
通常のファイル記述子の割り当てのいずれかになります。
主に. _ssh
たとえば、次の 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'
ただし、このコマンドを非 TTYssh
コマンドとして実行すると、STD ストリームは常にパイプされているように見えます。これを実証するには、より簡単な 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 ${?}'
ssh
これは、非 ttyコマンドがパイプ処理されているかどうかを bash スクリプトが判断する方法がないことを意味するため、非常に重要です。この残念な動作は、最近のバージョンのssh
が非 TTY STDIO にパイプを使用し始めたときに導入されたことに注意してください。以前のバージョンではソケットを使用していましたが、 [[ -S ]]
.
などのコンパイル済みユーティリティと同様の動作をする bash スクリプトを作成する場合、通常、この制限により問題が発生しますcat
。たとえば、cat
さまざまな入力ソースを同時に処理する際に次の柔軟な動作を可能にし、非 TTY または強制 TTYssh
が使用されているかどうかに関係なく、パイプ入力を受信しているかどうかを判断するのに十分スマートです。
ssh -t localhost 'echo piped | cat - <( echo substituted )'
ssh -T localhost 'echo piped | cat - <( echo substituted )'
パイプが関与しているかどうかを確実に判断できる場合にのみ、そのようなことを行うことができます。そうしないと、パイプまたはリダイレクトからの入力が利用できないときに STDIN を読み取るコマンドを実行すると、スクリプトがハングし、STDIN 入力を待機します。
この問題を解決しようとして、問題の解決に失敗するいくつかの手法を調べました。
stat
/dev/stdin ファイル記述子での使用[[ "${-}" =~ 'i' ]]
tty
およびを介して tty ステータスを調べるtty -s
ssh
経由でステータスを調べる[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]
仮想ファイルシステムをサポートする OS を使用している場合は/proc
、STDIO のシンボリック リンクをたどって、パイプが使用されているかどうかを判断できる場合があります。ただし、/proc
クロスプラットフォームの POSIX 互換ソリューションではありません。
私はこの問題を解決することに非常に興味を持っているので、Linux と BSD の両方で動作する POSIX ベースのソリューションが望ましい他の手法があれば教えてください。
使用しているシェルについては言及していませんが、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 関数で標準入力がパイプ処理されていると、ファイル記述子「3」が何らかの形で作成されるようです。