名前のないパイプのエラーに対する制御シーケンス

名前のないパイプのエラーに対する制御シーケンス
$ awk -v f=<(cmdmayfail) -e 'BEGIN { r = getline < f ; print r }'
-bash: cmdmayfail: command not found
0

上記の名前のないパイプの例では、awk名前のないパイプからのエラーはわかりません。

$ awk -v f=<(cmdmayfail || echo "control sequence" ) -e 'BEGIN { r = getline < f ; print r }'

このエラーを認識するにはawk、制御シーケンスといくつかのエラー情報を送信して上記のコードを使用できます。

fileすでに多くのファイル タイプを認識していることを考えると、このアプリケーションでawkエラー制御シーケンスを正当なファイルからのものとして誤って処理しないように使用できる適切な制御シーケンスはありますか? ありがとうございます。

答え1

が標準の Unix コマンドである場合はcommandmayfail、制御シーケンスの前に独自の複雑なテキスト (例 ) を記述するだけで、違いを理解できる__ERROR__CMDFAIL__:ようになります。awk

ただし、独自のソフトウェアやプロプライエタリ ソフトウェアも含める場合は、一般的な文字列を提供するのは困難です。プロプライエタリ コマンドの 1 つがそのような文字列を使用する可能性はありますが、可能性は低いです。エラー メッセージの一般的な設定を確認し、使用される可能性が低い文字列を作成する必要があります。

commandmayfailがである場合file、質問が示唆するように、 のない文字列を使用すれば十分な場合があります:

答え2

からの出力がcmdmayfail比較的小さく、コマンド自体がコードの他の部分とは独立して終了する場合は、その出力を変数に格納し、終了ステータスを最初の行として渡すことができます。 のコードは<()次のようになります。

out="$(cmdmayfail)"; printf '%s\n' "$?" "$out"

終了ステータスを取得するawk必要があります。は実際の出力を読み取ります。getline<fgetline<fcmdmayfail

制限事項:

  • Bash の変数には null 文字を保存できません。
  • $()末尾の改行文字をすべて削除し、printf1 つだけ追加します。これを回避するには、面倒なトリックがあります。

    out="$(cmdmayfail; s="$?"; printf X; exit "$s")"; printf '%s\n%s' "$?" "${out%X}"
    

cmdmayfailブロック内で出力を処理するということは、早期終了をBEGIN期待していることを示している可能性がありますcmdmayfail。おそらくこの解決策で十分でしょう。


一般的には、cmdmayfailは「無限に」(つまり、終了するまで)実行される可能性があり、 の(「無限の」)標準入力を処理しながらその出力から読み取りたい場合がありますawk。このような場合、上記の解決策は機能しません。

からの出力の各行の先頭にcmdmayfail固定のステータス行 (例OK) を追加し、最後に の終了ステータスの行を追加することができますcmdmayfail。 のコードは<()次のようになります。

 cmdmayfail | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"

例:

$ (printf '%s\n' foo "bar baz"; exit 7) | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"
OK
foo
OK
bar baz
7

次に、awkコードを実行してgetline<f、 かどうかを確認しますOK。 そうであれば、次の行 (getline<f再び ) はcmdmayfail確実に です。 期待どおりに がなくなるまで、すべての行を解析するループを実行しますOK。 その後、終了ステータスになります。

これはcmdmayfail不完全な行。 例:

$ (printf 'foo\nincomplete line'; exit 22) | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"

の実装によってはsed、ツールは

  1. 不完全な行を無視するか、
  2. それを処理し、不足している改行文字を追加するか、
  3. そのまま処理します。

実際には、

  1. 出力の一部が欠落したり、
  2. 行が不完全であることを知らない、または
  3. 終了ステータスが付加された行を取得します。

(3) の場合はprintf '\n%s\n' "${PIPESTATUS[0]}"役立つかもしれません。最後の行cmdmayfailが完了した場合は追加の空行が生成されます。これにより、awkコードでそれを認識できるようになります。

が行の途中で強制的に終了された場合を考えてみましょう。その場合、不完全な行を解析したくないかもしれません。問題は、行の形式が完全かどうかcmdmayfailを知るには、次の (ステータス) 行をテストする必要があることです。このために有用なロジックを で実装するのは、少なくとも不便かもしれません。awkcmdmayfailawk


不完全なラインをできるだけ早く検出することは良いことです。readBashではこれができる欠点はread遅いことです (Bash 変数には null 文字を保存できないことに注意してください)。 解決例:

# define this helper function in the main shell
encerr () { ( eval "$@" ) | (while IFS= read -r line; do printf 'C\n%s\n' "$line"; done; [ -n "$line" ] && printf 'I\n%s\n' "$line") ; printf 'E\n%s\n' "${PIPESTATUS[0]}"; }
# this part you want to put in <()
encerr cmdmayfail

次に、内部のカスタム プロトコルをデコードする必要がありますawk。行はペアになっています。(プロトコルをより直感的に理解するには、以下の例を参照してください。)

  1. ペア(getline<f)から最初の行を読み取ります。
  2. 最初の行を変数(first=$0)に格納します。
  3. ペア(getline<f)から2行目を読みます。
  4. 最初の行($first)を分析します。
    • の場合、C2 番目 (現在の$0) は からの完全な行なのでcmdmayfail、解析できます。
    • の場合、I2 番目は からの不完全な行なのでcmdmayfail、解析するかどうかは任意です。E次のペアでは を期待します。
    • の場合、E2 番目は からの終了ステータスですcmdmayfail。これ以上のペアは期待しないでください。
  5. ループ。

eval "$@"関数内で使用しました。その後に書いたものはencerr2回目に評価されるので、通常は次のようなコードを実行します。

encerr 'cmd1 -opt foo'

または

encerr "cmd1 -opt foo"

あるいは

encerr 'cmd1 -opt foo | cmd2'

基本的にこれは、 でリモート コマンドを実行するために使用する形式ですssh。比較:

ssh a@b 'cmd1 -opt foo | cmd2'

または、次のように関数を構築することもできます。

encerr () { "$@" | …

そして次のように呼び出します:

encerr cmd1 -opt foo

比較対象sudo:

sudo cmd1 -opt foo

例( の元の関数を使用eval):

  • 空の出力で成功

    $ encerr true
    E
    0
    
  • 空の出力で失敗

    $ # I'm redirecting stderr so "command not found" doesn't obfuscate the example
    $ encerr nonexisting-command-foo1231234 2>/dev/null
    E
    127
    
  • 完全なラインの後の成功

    $ encerr 'date; sleep 1; date'
    C
    Mon Sep 30 09:07:40 CEST 2019
    C
    Mon Sep 30 09:07:41 CEST 2019
    E
    0
    
  • 完全なラインの後の失敗

    $ encerr 'printf "foo\nbar\n"; false'
    C
    foo
    C
    bar
    E
    1
    
  • 不完全なラインの後の成功

    $ encerr 'printf "foo bar\n89 baz"'
    C
    foo bar
    I
    89 baz
    E
    0
    
  • 不完全な行の後の失敗

    $ encerr 'printf "\nThe first line was empty and this one was interru"; exit 33'
    C
    
    I
    The first line was empty and this one was interru
    E
    33
    

関連情報