シェルスクリプトがパイプを介して実行されているかどうかを検出するにはどうすればよいですか?


252

標準出力が端末に送信されているか、別のプロセスにパイプされているかをシェルスクリプト内から検出するにはどうすればよいですか?

要点:出力を色分けするためにエスケープコードを追加したいのですが、対話的に実行した場合のみで、パイプで実行した場合ls --colorはそうではありません。


2
ここにいくつかの興味深いテストケースがあります!<a href=" serverfault.com/questions/156470/... stdin</a>に待っているスクリプトの

2
@ user940324正しいリンクがあるserverfault.com/q/156470/197218
Palec

回答:


385

純粋な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は通常のファイル記述子割り当ての1つです。

0:     stdin  
1:     stdout  
2:     stderr

1
@Kelvinそこにあるmanページのスニペットは、そうすべきであると示唆していますが、それらのファイル記述子はデフォルトでは割り当てられていません。
dmckee ---元モデレーターの子猫

41
明確にするために、-tフラグはPOSIXで指定されているため、すべてのPOSIX互換シェルで機能するはずです(つまり、bash拡張ではありません)。 pubs.opengroup.org/onlinepubs/009695399/utilities/test.html
FireFly

スクリプトをsshリモートコマンドとして実行する場合にも機能します。史上最高の答えと非常にシンプル。
linux_newbie 2016

編集(改訂5)後の回答は改訂3よりも明確で、事実も正しいことに同意します(「印刷」がより正確になる場所で「リターン」が非常に非公式に使用されることを無視して)。
Palec 2017年

fishシェルの答えを探していました。使用testはきちんとしていますが、括弧で囲まれた例はサポートされていないため、試すことはできません。類似のものでラップしようとしましたbegin; ...; endが、それはうまくいかなかったようで、ポジティブコードブロックをもう一度実行しました。使用する必要statusがあるかもしれませんが、それは配管をチェックしていないようです。これらの明確な回答のおかげで、私は基本的に、先行するコマンド/スクリプトのSTDOUTが端末に設定されていないかどうかを確認したいと思います。
Pysis

126

主にのようなプログラムが原因で、STDIN、STDOUT、またはSTDERRがスクリプトとの間でパイプ接続されているかどうかを判別するための確実な方法はありません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'

しかし、彼らはいつもうまくいくとは限らない

ただし、このコマンドを非TTYコマンドとして実行するとssh、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 ${?}'

なぜそれが重要なのか

tty以外のsshコマンドがパイプ処理されているかどうかをbashスクリプトで確認する方法がないことを意味するため、これはかなり大きな問題です。この不幸な動作は、の最近のバージョンがssh非TTY STDIOのパイプの使用を開始したときに導入されたことに注意してください。以前のバージョンではソケットを使用していましたが、これを使用してbash内から区別できます[[ -S ]]

それが重要なとき

通常、この制限により、などのコンパイルされたユーティリティと同様の動作をするbashスクリプトを作成するときに問題が発生しますcat。たとえば、catさまざまな入力ソースを同時に処理する次の柔軟な動作を可能にし、非TTYまたは強制TTY sshが使用されているかどうかに関係なく、パイプ入力を受信して​​いるかどうかを判断するのに十分なほどスマートです。

ssh -t localhost 'echo piped | cat - <( echo substituted )'
ssh -T localhost 'echo piped | cat - <( echo substituted )'

パイプが関係しているかどうかを確実に判断できる場合にのみ、そのようなことを行うことができます。それ以外の場合、パイプまたはリダイレクトからの入力が利用できないときにSTDINを読み取るコマンドを実行すると、スクリプトがハングし、STDIN入力を待機します。

機能しないその他のこと

この問題を解決するために、問題を解決できないいくつかの手法を調べました。

  • SSH環境変数の調査
  • 使用してstatは/ dev / STDINファイル記述子に
  • インタラクティブモードを調べる [[ "${-}" =~ 'i' ]]
  • ttyおよびを介したttyステータスの調査tty -s
  • ssh経由でステータスを調べる[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

/proc仮想ファイルシステムをサポートするOSを使用している場合は、STDIOのシンボリックリンクをたどってパイプが使用されているかどうかを判断できる場合があります。ただし、/procクロスプラットフォームのPOSIX互換ソリューションではありません。

この問題を解決することは非常に興味深いので、他の有効な手法、できればLinuxとBSDの両方で機能するPOSIXベースのソリューションについて考えた場合はお知らせください。


2
環境変数またはプロセス名を明確に検査することは、非常に信頼できないヒューリスティックです。しかし、他のヒューリスティックがこの目的に適さない理由、またはそれらの問題が何かを少し拡張できますか?たとえばstat、/ dev / stdinの呼び出しの出力に違いはありません。そして、なぜない"${-}"tty -s動作しませんか?私はまた、ソースコードを調べましたcatが、POSIXシェルでは実行できない魔法を実行している部分を確認できませんでした。拡大していただけますか?
josch

30

コマンドtest(に組み込まれbashています)には、ファイル記述子がttyであるかどうかをチェックするオプションがあります。

if [ -t 1 ]; then
    # stdout is a tty
fi

「を参照してくださいman test」または「man bash」と「で検索-t


3
/ usr / bin / testは
組み込み

4
FireFlyがdmckeeの回答で述べたように、-tを実装しないシェルはPOSIXに準拠していません。
scy 2013年

また、bashの組み込みの参照してくださいhelp test(とhelp help、より多くのために)、info bashより詳細な情報については。これらのコマンドは、オフラインでスクリプトを作成する場合や、より広い理解を得たい場合に最適です。
Joel Purra、2015年

13

使用しているシェルについては言及していませんが、Bashではこれを行うことができます。

#!/bin/bash

if [[ -t 1 ]]; then
    # stdout is a terminal
else
    # stdout is not a terminal
fi

6

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

:# 

興味深いことに、テストのためにSolarisにアクセスできればいいのにと思います。Solarisインスタンスが「/ proc」ファイルシステムを使用している場合、stdin、stdout、およびstderrの「/ proc」シンボリックリンクの検索を含む、より信頼性の高いソリューションがあります。
Dejay Clayton

1

次のコード(linux bash 4.4でのみテスト済み)は移植性があるとは見なされず、推奨もされません。ただし、ここでは完全を期すために記載しています。

ls /proc/$$/fdinfo/* >/dev/null 2>&1 || grep -q 'flags: 00$' /proc/$$/fdinfo/0 && echo "pipe detected"

理由はわかりませんが、bash関数がSTDINをパイプ処理したときに、ファイル記述子「3」が何らかの方法で作成されたようです。

それが役に立てば幸い、

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.