Bashコマンドのグループ化とパイプライン状態を組み合わせる方法

Bashコマンドのグループ化とパイプライン状態を組み合わせる方法

Bashコマンドのグループ化とパイプライン状態を組み合わせる方法は?

以下は、コマンドグループの例です。

{ tar -cf - my_folder 2>&1 1>&3 | grep -v "Removing leading" 1>&2; } 3>&1 | gzip --rsyncable > my_file.tar.gz


以下は、上記と一致するパイプライン状態の読み取りの例です。

[[ ${PIPESTATUS[*]} =~ [1-9] ]] && rm my_file.tar.gz


この例では、コマンドグループは、cronを介して渡された「tarから先行/削除」に関する警告からメールプールを除外します。これはstderrに属するためです(Unicesにはstdwarnが不足し、tarにはQuietオプションが不足しています)。実際のエラーはそのままにしておきます。

パイプ状態の読み取りは、破損したバックアップファイルがすぐに削除されることを保証し、後で標準のFIFOアルゴリズムを使用してクリーンアップ中に有効な古いファイルが削除されるのを防ぎます。

ただし、この例は機能しません。上記のパイプライン状態には、grepとgzipの終了コードである[1 0]が含まれていますが、tarは含まれていません。

私が試した1つの試みは次のとおりです。

{ tar -cf - my_folder 2>&1 1>&3 | grep -v "Removing leading" 1>&2; GROUPSTATUS=${PIPESTATUS[*]}; } 3>&1 | gzip --rsyncable > my_file.tar.gz
[[ $GROUPSTATUS =~ [1-9] ]] && rm my_file.tar.gz

しかし、グループを退会した後、GROUPSTATUSは空です。

(GROUPSTATUSを一部のリテラルなどのパイプライン状態以外に設定することで、この変数が実際に一般的な状況でコマンドのグループ化範囲をエスケープしていることを確認できます。)

returnまた、最初のパイプラインコンポーネントの終了コードをグループ内から外部に渡すことを試みましたが、コマンドグループに返すと、bashはエラーメッセージを生成します。

答え1

パイプラインを実行すると、パイプで区切られた各要素が独自のプロセスで実行されます。変数の割り当ては、独自のプロセス内でのみ適用されます。 ksh と zsh では、パイプの最後の要素は元のシェルで実行されます。他のシェル(bashなど)では、各パイプ要素が独自のサブシェルで実行され、元のシェルがすべて完了するのを待ちます。

$ bash -c 'GROUPSTATUS=foo; echo GROUPSTATUS is $GROUPSTATUS'
GROUPSTATUS is foo
$ bash -c 'GROUPSTATUS=foo | :; echo GROUPSTATUS is $GROUPSTATUS'
GROUPSTATUS is 

あなたの場合、すべての成功したコマンドに興味があるので、ステータスコードを上に流すことができます。

{ tar -cf - my_folder 2>&1 1>&3 | grep -v "Removing leading" 1>&2;
  ! ((PIPESTATUS[0])); } 3>&1 |
gzip --rsyncable > my_file.tar.gz;
if ((PIPESTATUS[0] || PIPESTATUS[1])); then rm my_file.tar.gz; fi

パイプの左側から8ビット以上の情報が必要な場合は、他のファイル記述子に書き込むことができます。以下は原理証明の例です。

{ { tar …; echo $? >&4; } | …; } | { gzip …; echo $? >&4; } \
  4>&1 | ! grep -vxc '0'

stdoutにデータがある場合は、コマンド置換を使用してシェル変数に入れることができます。つまり、$(…)コマンドの置換はコマンドの stdout から読み取られるので、スクリプトの stdout にもコンテンツを印刷する場合は一時的に渡す必要があります。ファイル記述子。次のコードスニペットは、fd 3を使用してスクリプトのstdoutで終わる内容を示し、fd 4を使用してキャプチャされた内容を示します$statuses

statuses=$({ { tar -v … >&3; echo tar $? >&4; } | …; } |
           { gzip …; echo gzip $? >&4; } 4>&1) 3>&1

他のコマンドの出力を別の変数としてキャプチャする必要がある場合は、bash、ksh、zshなどの「高度な」シェルにも直接的な方法はないと思います。以下はいくつかの回避策です。

  • 一時ファイルを使用してください。
  • 単一の出力ストリームを使用します。たとえば、各行にはソースを表すプレフィックスがあり、最上位レベルでフィルタリングされます。
  • PerlやPythonなどの高度な言語を使用してください。

答え2

この問題を解決する簡単な方法は、 my_folder をシェル変数に入れて、前のスラッシュを削除することです。

tar -cf - ${my_folder##/} | gzip --rsyncable > my_file.tar.gz

それ以外の場合は、ブロック内の ${PIPESTATUS[0]} を確認し、ゼロでない場合は false を使用して外部プロセスにエラーを通知できます。

{ tar -cf - my_folder 2>&1 1>&3 | 
    grep -v "Removing leading" 1>&2; 
    [ ${PIPESTATUS[0]} -eq 0 ] && true || false; } 3>&1 | 
    gzip --rsyncable > my_file.tar.gz

関連情報