終了コードの結果に基づいてコマンドの標準出力をパイプする方法

終了コードの結果に基づいてコマンドの標準出力をパイプする方法

次から展開この問題、コマンドが成功したかどうかに応じて、コマンドの標準出力をパイプ処理するユースケースがあります。

デフォルトのパイプラインで始まります。

command | grep -P "foo"

commandしかし、時には標準出力には何も印刷されませんが、実際には終了コードがあることを確認しました。この状況を無視し、終了コードが次の場合にのみエラーコードを出力したいと0思います。grep1

実際の例として、次のコマンドを実装できます。

OUTPUT=$(command) # exit code is 0 or 1
RESULT=$?
if [ $RESULT -eq 0 ]; then
  return $RESULT; # if the exit code is 0 then we simply pass it forward
else
  grep -P "foo" <<< $OUTPUT; # otherwise check if the stdout contains "foo"
fi

しかし、ここにはスクリプトを書く必要があるという欠点がたくさんあります。つまり、コンソールでしか実行できないという意味です。これもちょっと素人的なようです。

1よりきれいな構文のために、終了コードがある場合は終了コードを前方にパイプし、そうでなければ終了コードを前方にパイプする仮想三項演算子を想像してみてください。

command |?1 grep -P "foo" : $?

この結果を達成できる一連のオペレータとユーティリティはありますか?

答え1

パイプラインのコマンドは同時に実行されます。これはパイプとプロセス間の通信メカニズムのすべてです。

存在する:

cmd1 | cmd2

cmd1cmd2同時にcmd2記録されたデータの処理を開始しますcmd1

cmd2失敗時にのみ開始する場合は、cmd1終了ステータスを報告してcmd2から開始する必要があるため、cmd1パイプは使用できず、cmd1生成されたすべてのデータを保持するために一時ファイルを使用する必要があります。

 cmd1 > file || cmd2 < file; rm -f file

あるいは、あなたの例のようにメモリに保存しますが、ここには他の多くの問題があります(たとえば、末尾の改行を$(...)すべて削除し、ほとんどのシェルは大きな出力のサイズ変更の問題は言うまでもなく、NULバイトを処理できません) 。

zshLinuxでは、または同じシェルを使用してbashここに文書を保存し、ここに文字列を一時ファイルに保存するには、次のようにします。

{ cmd1 > /dev/fd/3 || cmd2 <&3 3<&-; } 3<<< ignored

シェルに一時ファイルの作成とクリーンアップを処理させます。

bash バージョン 5 は一時ファイルを作成した後、そのファイルに対する書き込み権限を削除するため、上記の方法は機能しません。この問題を解決するには、まず書き込み権限を復元する必要があります。

{ chmod u+w /dev/fd/3
  cmd1 > /dev/fd/3 || cmd2 <&3 3<&-; } 3<<< ignored

手動で、POSIX的に:

tmpfile=$(
  echo 'mkstemp(template)' |
    m4 -D template="${TMPDIR:-/tmp}/XXXXXX"
) && [ -n "$tmpfile" ] && (
  rm -f -- "$tmpfile" || exit
  cmd1 >&3 3>&- 4<&- ||
    cmd2 <&4 4<&- 3>&-) 3> "$tmpfile" 4< "$tmpfile"

mktemp一部のシステムには、一時ファイルの生成をより簡単にする非標準コマンド(システムごとに異なるインタフェースがあります)があります(tmpfile=$(mktemp)一部のシステムではファイルを生成しないため、調整が必要な場合がありますが、ほとんどの実装では十分ですumask)。 。これは互換性のある実装には[ -n "$tmpfile" ]必要ありませんが、GNUは呼び出しが失敗したときにゼロ以外の終了ステータスを返さないm4ため、少なくとも互換性がありません。m4mkstemp()

また、コードの実行を防ぐことができるものは何もありません。快適。あなたの「スクリプト」これは、対話型シェルのプロンプトで同じ方法で入力できますが(returnコードが関数にあると仮定される部分を除く)、次のように単純化できます。

output=$(cmd) || grep foo <<< "$output"

答え2

注:質問にタグが付けられているため、bash機能が利用可能であると仮定します。

サンプルコードを見ると、次のようなことをしたいようです。

  • コマンドの終了ステータスが 0 の場合、コマンドの終了ステータスが使用されます。
  • grepそれ以外の場合は、終了ステータスを使用してください。

空のパイプで実行するとgrepコストがかからないので、とにかくパイプを使用してコマンドの終了状態を確認することをお勧めします。PIPESTATUS配列を使用してbashから取得できます。

$ (echo foo; exit 1) | grep foo
foo
$ echo "${PIPESTATUS[@]}"
1 0  

ここで、サブシェルは 1 で終了し、grep は 0 で終了します。

だからあなたはこれを行うことができます:

(command | grep -P "foo"; exit "$((PIPESTATUS[0] ? $? : 0))")

表現は単純化できますが、アイデアを得ることができます。


単に出力を偽にするオプションもあります。

(command && echo foo) | grep -P foo

echo foowhereはコマンドが成功した場合にのみ実行されるため、grepも成功する可能性があります。

関連情報