コマンド出力をstdoutに送信し、エラーをstderrに送信している間にコマンドをgrepにパイプして終了ステータスを取得するにはどうすればよいですか?

コマンド出力をstdoutに送信し、エラーをstderrに送信している間にコマンドをgrepにパイプして終了ステータスを取得するにはどうすればよいですか?

特定のユースケース:

私が使用したいcurlURLは-v、詳細なデバッグ情報を通常のようにstderrに出力し、応答の本文を通常のようにstdoutに出力しますが、正規表現が応答にあることを確認し、見つからない場合はゼロ以外のgrep値で終了します.このコマンドの最も簡単な形式は次のとおりです。

curl -v "${url}" | grep -q "${pattern}"

ただし、grep応答本文は消費されます。

私が試したこと:

  1. 私は見たことがない」一致するラインだけでなく、すべてのラインを出力するようにgrepを説得します。「これは次のことを示唆しています。

    curl -v "${url}" | grep --color -E "${pattern}|$"
    

    ただし、この色だけが一致を強調表示するため、pattern存在しない場合はゼロ以外の終了コードは提供されません。

  2. 私も見たことがある」grep終了コードを取得しますが、すべての行を印刷する方法は?「これは次のことを示します。

    curl -v "${url}" | tee /dev/stderr | grep -q "${pattern}"
    

    終了コードで終了し、応答本文を印刷しますが、応答本文をstderrに印刷するので、stderrとstdoutストリームを別々に保持したいとgrep思います。curl

  3. 次のように」パイプによる直接出力と標準出力「、私は努力した

    curl -v "${url}" | tee >(grep -q "${pattern}")
    

    しかし、これはすべてを印刷し、stdoutストリームとstderrストリームを正しく分離しますが、grep終了コードは削除されます。

現在の解決策:

これまで私が考えることができる唯一の方法は、応答本文を一時ファイルに貼り付けることです。

curl -v "${url}" | tee /tmp/response
grep -q "${pattern}" /tmp/response

これにより、正しい出力と正しい終了コードが表示されます。

しかし、一時ファイルなしで単一のパイプでこれを行う方法はありますか?

答え1

2番目の答えは正しい方向に行きます。努力する

{ curl -v "${url}" | tee /dev/fd/3 | grep -q "${pattern}";} 3>&1

簡単な説明:

  • ファイル記述子3>&11 (標準出力) をファイル記述子 3 (標準使用のない最低記述子) にコピーします。これは、パイプライン全体(つまり、コマンドライン全体)の標準出力を表します。新しいファイル記述子(3)はパイプライン全体に有効です。

    任意の数字を使用できます(少なくとも最大9まで)。 POSIX シェルコマンド言語仕様、 2.7 リダイレクト、説明する

    …すべての実装は少なくとも0から9までサポートする必要があります…

    POSIX 互換シェルは 9 より大きい数字を認識しない場合があります。そして bash(1)説明する

    9より大きいファイル記述子を使用するリダイレクトは慎重に使用する必要があります。

  • tee /dev/fd/3teeファイル記述子3に書き込むように指示するので、teeパイプの標準出力に接続されます。

    • ディレクティブで使用したのと同じ番号をここで使用する必要がありますn>&1
    • Linux以外のオペレーティングシステムでは、同様のファイル名が機能しない可能性があります。/dev/fd/n

    これはstdoutとは何の関係もありません。tee, これはパイプラインです  grep

スティーブン・チャジェラス指摘:

grep -qパターンが見つかったら終了します。teeその後、出力が書き込まれると終了します。 GNU の実装は、  tee少なくとも  -p SIGPIPE を無視し、出力を取得するターゲットに引き続き書き込むことができます。さらに、一部のシェルはパイプラインの最後のコンポーネントのみを待機するため、ここでパターンが見つかるとスクリプトの残りのgrep -q部分が渡されます。

正式に最初のことを確認しました。 (パターンが一致するとパイプが早く終了する可能性があります)1

私は両方の問題を解決するために、次の改善されたソリューションを提供します。

{ curl -v "${url}" | tee /dev/fd/3 | { grep -q "${pattern}" && cat > /dev/null;} } 3>&1

メモ:

  • データフローならいいえパターンと一致すると、grepストリーム全体(つまり入力)を読み取るため、問題は発生しません。そして、この場合はgrep「失敗」するので、  &&実行されず、複合コマンドはからcat終了コードを返します。grepつまり、失敗します(つまり、一致しません)。
  • データフローならするパターンが一致すると(Stéphaneが指摘したように)、パターンが一致すると終了し、入力 grep全体(つまり出力curl | tee)は読み取られません。しかしこの場合にはgrep「成功」するので  &&、 cat 〜する実行すると、残りのデータが吸収されます(EOFまで)。これにより、teeデータストリーム全体がパイプに書き込まれ、  curlすべての出力が処理されるまでパイプ(つまりコマンドライン全体)は終了しません。

技術的には、これはまだ問題を残します。データフローがパターンと一致してgrep「成功」すると、パイプラインの終了状態はの終了状態になりますcat。なぜ失敗するのかわかりませんが、(pipe) | cat > /dev/null理論的には可能です。これを防ぐために、私は以下を提案します。

{ curl -v "${url}" | tee /dev/fd/3 | if grep -q "${pattern}"; then cat > /dev/null; true; else false; fi; } 3>&1

成功した場合は明示的にtrueを返しgrep、失敗した場合はfalseを返します。
______________
1つの  オプションが追加され-pました。teeバージョン 8.24 (2015-07-03)


上記の変形のうち、必要に応じて 管路他の場所からの出力の場合は、通常どおりcurlコマンドラインの末尾にパイプを追加するだけです。

{ curl …; fi; } 3>&1 | lpr

しかし、望むならリダイレクトファイルに保存するには、出力リダイレクトを挿入する必要があります。今後これ  3>&1

{カール...;手数料;}>結果ファイル3>&1

「ファイルへのパイピング」は間違った用語であることを覚えておいてください。

答え2

ここに特別なことがない場合は、grepawkを使用してパターンマッチングを実行することもできます。

curl -v "${url}" | awk -v p="$pattern" '$0 ~ p {found=1} {print} END {exit ! found}'
  • -v p="$pattern"awk 変数をpシェル変数の値に設定します。pattern
  • $0 ~ p {found=1}foundpが変数の正規表現と一致する場合、awk変数は1に設定されます。
  • {print}- すべての行を印刷します(このブロックには条件が指定されていないため)。
  • END {exit ! found} - 入力が終了すると負の状態で終了しますfound(パターンが一致した場合は0、それ以外の場合は1)。

awkとgrepはさまざまな正規表現をサポートしているので、パターンを変更する必要があるかもしれません。

関連情報