パイプが空であるかどうかを確認し、空でない場合はデータに対してコマンドを実行する方法は?


42

bashスクリプトで行をパイプし、プログラムに渡す前にパイプにデータがあるかどうかを確認したいと思います。

私が見つけた検索ですが、test -t 0ここでは機能しません。常にfalseを返します。では、パイプにデータがあることを確認する方法は?

例:

echo "string" | [ -t 0 ] && echo "empty" || echo "fill"

出力: fill

echo "string" | tail -n+2 | [ -t 0 ] && echo "empty" || echo "fill"

出力: fill

前述のパイプラインが出力を生成したかどうかをテストする標準/標準的な方法とは異なりますか?入力をプログラムに渡すために保存する必要があります。これは、あるプロセスから別のプロセスに出力をパイプする方法を一般化しますが、最初のプロセスに出力がある場合にのみ実行しますか?電子メールの送信に焦点を当てています。


回答:


37

一般に利用可能なシェルユーティリティを使用してパイプの内容を覗く方法も、パイプに文字を読み取って戻す方法もありません。パイプにデータがあることを知る唯一の方法は、バイトを読み取ることです。そして、そのバイトを宛先に取得する必要があります。

そのため、1バイトを読み取ります。ファイルの終わりを検出した場合、入力が空のときにやりたいことを行います。バイトを読み取った場合、入力が空でないときに実行したいことを分岐し、そのバイトをそのバイトにパイプし、残りのデータをパイプします。

first_byte=$(dd bs=1 count=1 2>/dev/null | od -t o1 -A n | tr -dc 0-9)
if [ -z "$first_byte" ]; then
  # stuff to do if the input is empty
else
  {
    printf "\\$first_byte"
    cat
  } | {
    # stuff to do if the input is not empty
  }      
fi

Joey Hessのmoreutilsifneユーティリティは、入力が空でない場合にコマンドを実行します。通常、デフォルトではインストールされませんが、ほとんどのUNIXバリアントで利用可能または簡単に構築できるはずです。入力が空の場合、何もせず、ステータス0を返します。ステータス0は、正常に実行されているコマンドと区別できません。入力が空の場合に何かを実行したい場合は、コマンドが0を返さないように調整する必要があります。これは、成功の場合に識別可能なエラーステータスを返すことで実行できます。ifne

ifne sh -c 'do_stuff_with_input && exit 255'
case $? in
  0) echo empty;;
  255) echo success;;
  *) echo failure;;
esac

test -t 0これとは何の関係もありません。標準入力が端末かどうかをテストします。入力が利用可能かどうかについては、何も言いません。


STREAMSベースのパイプを備えたシステム(Solaris HP / UX)では、I_PEEK ioctlを使用して、パイプを消費することなくパイプの内容を覗くことができると信じています。
ステファンChazelas

@StéphaneChazelasは、残念ながら* BSDのパイプ/ FIFOからデータを覗く方法がないため、パイプの量だけでなく、パイプから実際のデータを返すポータブル peekユーティリティを実装する見通しはありません。(4.4 BSD、386BSDなどのパイプはソケットペアとして実装されていましたが、それは* BSDの以降のバージョンでは破壊されました-双方向を維持していました)。
mosvy

bashには、aを介して公開された入力をチェックするルーチンがありますread -t 0(この場合、tはタイムアウトを意味します)。
アイザック

11

単純な解決策は、ifneコマンドを使用することです(入力が空でない場合)。一部のディストリビューションでは、デフォルトではインストールされません。moreutilsほとんどのディストリビューションのパッケージの一部です。

ifne 標準入力が空でない場合にのみ、指定されたコマンドを実行します

標準入力が空でない場合、ifne指定されたコマンドに渡されることに注意してください


2
2017年現在、MacまたはUbuntuにはデフォルトではありません。
スリダールサルノバト

7

古い質問ですが、誰かが私と同じように遭遇した場合:私の解決策はタイムアウトで読むことです。

while read -t 5 line; do
    echo "$line"
done

stdinが空の場合、5秒後に戻ります。それ以外の場合は、すべての入力を読み取り、必要に応じて処理できます。


私はこのアイデアが好き-tですが、残念ながらPOSIXの一部ではありません:pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html
JepZ

6

stdin(0)のファイル記述子が開いているか閉じているかを確認します。

[ ! -t 0 ] && echo "stdin has data" || echo "stdin is empty"

データを渡して、あるかどうかを確認したい場合は、とにかくFDに合格するので、これも良いテストではありません。
-Jakuje

1
[ -t 0 ]ttyに対してfd 0が開かれているかどうかをチェックします。閉じているか開いているかはチェックしません。
mosvy

@mosvyは、スクリプトでそのソリューションを使用することの影響について詳しく説明していただけますか?動作しない場合はありますか?
JepZ

@JepZハァッ?./that_script </dev/null=>「stdinにはデータがあります」。または./that_script <&-、標準入力を実際に閉じます。
mosvy

5

test -s /dev/stdin(明示的なサブシェルで)使用することもできます。

# test if a pipe is empty or not
echo "string" | 
    (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

echo "string" | tail -n+2 | 
    (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

: | (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

8
私にはうまくいきません。常にパイプが空であると言います。
アンフェタ

2
Macでは動作しますが、Linuxボックスでは動作しません。
ピーク

3

bashの場合:

read -t 0 

入力にデータがあるかどうかを検出します(何も読み込まない)。次に、入力を読み取ることができます(読み取りの実行時に入力が使用可能な場合)。

if     read -t 0
then   read -r input
       echo "got input: $input"
else   echo "No data to read"
fi

注:これはタイミングに依存することを理解してください。これは、read -t実行時にのみ入力に既にデータがあるかどうかを検出します。

たとえば、

{ sleep 0.1; echo "abc"; } | read -t 0; echo "$?"

出力は1(読み取りエラー、つまり、空の入力)です。エコーはいくつかのデータを書き込みますが、最初のバイトの開始と書き込みはそれほど速くありません。したがって、read -t 0プログラムはまだ何も書き込んでいないため、入力が空であることを報告します。


github.com/bminor/bash/blob/…-これは、bashがファイル記述子に何かがあることを検出する方法のソースです。
パベルパトリン

ありがとう@PavelPatrin
Isaac

1
@PavelPatrin それは動作しません。リンクから明らかなbashように、a select()またはanのいずれか、またはioctl(FIONREAD)両方ではなく、両方ではなく、動作するはずです。read -t0は壊れてます。使用しないでください
mosvy

ああ、今日は何が悪いのかを2時間理解しようとしています!ありがとう、@ mosvy!
パベルパトリン

3

Unixで読み取り可能なデータがあるかどうかを確認する簡単な方法の1つは、FIONREADioctlを使用することです。

私はそれを行う標準的なユーティリティを考えることができないので、ここにそれを行う簡単なプログラムがあります(ifnemoreutils IMHO よりも良い;-))。

fionread [ prog args ... ]

stdinに使用可能なデータがない場合、ステータス1で終了します。データがある場合、実行しprogます。no progを指定すると、ステータス0で終了します。

すぐに利用可能なpollデータのみに関心がある場合は、呼び出しを削除できます。これは、パイプだけでなく、ほとんどの種類のfdsで機能するはずです。

fionread.c

#include <unistd.h>
#include <poll.h>
#include <sys/ioctl.h>
#ifdef __sun
#include <sys/filio.h>
#endif
#include <err.h>

int main(int ac, char **av){
        int r; struct pollfd pd = { 0, POLLIN };
        if(poll(&pd, 1, -1) < 0) err(1, "poll");
        if(ioctl(0, FIONREAD, &r)) err(1, "ioctl(FIONREAD)");
        if(!r) return 1;
        if(++av, --ac < 1) return 0;
        execvp(*av, av);
        err(1, "execvp %s", *av);
}

このプログラムは実際に機能しますか?POLLHUP空のケースを処理するためにイベントも待つ必要はありませんか?パイプの反対側に複数のファイル記述がある場合、それは機能しますか?
ジル「SO-悪であるのをやめ

はい、動作します。POLLHUPはポーリングによってのみ返されます。POLLINを使用してPOLLHUPを待つ必要があります。パイプの端まで開いているハンドルの数は関係ありません。
mosvy

参照unix.stackexchange.com/search?q=FIONREAD+user%3A22565のperlからFIONREADを実行する方法については、(より一般的に利用可能なコンパイラより)
ステファンChazelas

FIONREAD(またはHAVE_SELECT)を使用するルーチンは、ここでbashに実装されています
アイザック

2

短くて不可解なワンライナーが好きな場合:

$ echo "string" | grep . && echo "fill" || echo "empty"
string
fill
$ echo "string" | tail -n+2 | grep . && echo "fill" || echo "empty"
empty

元の質問の例を使用しました。パイプされたデータを使用したくない場合は-q、grepでオプションを使用します


0

これは、最初の行全体を読んでよければ、bashでのifneの合理的な実装のようです

ifne () {
        read line || return 1
        (echo "$line"; cat) | eval "$@"
}


echo hi | ifne xargs echo hi =
cat /dev/null | ifne xargs echo should not echo

5
readまた、入力が空ではないが改行文字を含まない場合はfalseを返し、入力に対してread何らかの処理を行い、として呼び出さない限り複数行を読み取ることがありますIFS= read -r lineecho任意のデータには使用できません。
ステファンシャゼル

0

これは私にとって read -rt 0

データなしの元の質問の例:

echo "string" | tail -n+2 | if read -rt 0 ; then echo has data ; else echo no data ; fi

いいえ、機能しません。{ sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }(偽陰性)とtrue | { sleep .1; read -rt0 && echo YES; }(偽陽性)を試してください。実際、書き込み専用モードでread開かれたfdsでもbash はだまされます。それがするように見える唯一のものはそのfdにあります。{ read -rt0 && echo YES; cat; } 0>/tmp/fooselect(2)
mosvy

...そしてselect、fd read(2)がブロックされない場合、fdが「準備完了」として返されEOFます。結論:read -t0壊れていbashます。使用しないでください。
mosvy

@mosvy bashbugで報告しましたか?
アイザック

@mosvy { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }は、(読み取りが実行された時点で)入力がないため、偽陰性ではありません。後で(sleep .1)入力利用可能になりました(猫用)。
アイザック

@mosvy rオプションが検出に影響するのはなぜですか?:echo "" | { read -t0 && echo YES; }YESを出力しますが、実行echo "" | { read -rt0 && echo YES; }しません。
アイザック
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.