パイプの最初のコマンドが失敗したときに set -o pipefail を強制する方法

パイプの最初のコマンドが失敗したときに set -o pipefail を強制する方法

私は、Postgres データベースから bash のファイルにデータをエクスポートしようとしています。ただし、データベースへの接続が失敗しない場合にのみファイルが上書きされるようにしたい (つまり、データが返される)

使用してみましたパイプ故障オプションですが、最初のコマンドがエラーで失敗した場合 (たとえば、ホストが存在しない)、cat コマンドは引き続き実行され、空のファイルが生成されます (最後の有効なコンテンツが消去されるため、これを回避する必要があります)。以下の例では、myhost は無効なホストであるため、psql コマンドは単に失敗します。

したがって、より大きな問題は、pipefail が設定されている場合に、最初のコマンドが失敗したときに後続のコマンドが実行されないようにする方法です。

#!/bin/sh
set -o nounset
set -o errexit
set -o pipefail

PG_HOST=myhost

psql $PG_HOST -At -F$'\t' -c "SELECT * FROM mytable" | cat > /tmp/mytable.txt

答え1

set -o pipefail -o errexit後続のコマンドの実行を阻止しますが、それは役に立ちません。なぜなら、その後コマンドが実行されないようにします。パイプラインではproducer | consumer、コマンドproducerconsumerコマンドが実行されます。並行して異常なタイミングの事故がない限り、すでに起動しているので、失敗したconsumer場合は起動を防ぐことはできません。producer

consumer「成功して空でない出力を生成する」と「失敗して出力を生成しない」という2つの可能性しかない場合はconsumer、次のように使用できます。ifneJoey Hess の moreutils より

producer | ifne consumer

ただし、これはあなたのユースケースでは機能しないと思います。一致する行が存在しない可能性があり (偽陰性となり、データが古くなります)、データベース接続が途中で失われる可能性があります (偽陽性となり、データが切り捨てられます)。

プロデューサーが成功したかどうかを知る必要がある場合は、コンシューマーを開始する前にプロデューサーが完了するまで待つ必要があります。また、コンシューマーがまだ存在しないため、出力を保存するものが必要です。

出力にヌルバイトが含まれず、1 つの改行文字のみで終了し、大きすぎない場合は、シェル変数に保存できます。

output=$(producer); producer_status=$?
if [ "$producer_status" -ne 0 ]; then
  echo >&2 "Producer failed with status $producer_status"
  exit "$producer_status"
fi
printf '%s\n' "$output" | consumer

zsh や、ksh93 および bash を含む他のいくつかのシェルでは、最後の行は のように簡略化できますconsumer <<<"$output"

コマンド置換では末尾の改行が削除されることに注意してください。末尾の空行が関係する場合は、回避策として最初の行を次のように変更します。

output=$(producer; ret=$?; echo .; exit "$?")
producer_status=$? output=${output%?}

$outputすると、末尾の改行文字があればそれを含む完全な出力が含まれます。次に、 のprintf %s "$output"代わりにを使用してprintf '%s\n' "$output"、それを に渡しますconsumer

出力が大きすぎる場合や null バイトが含まれる可能性がある場合は、一時ファイルに保存します。

答え2

DopeGhotiが言ったように、pipefail...は単に、パイプチェーンのどの時点でもエラーが終了コードとして保存されることを意味します。[パイプラインの]

エラー時にスクリプトを終了するには、 を使用しますset -e

ファイルが作成されないようにするには、一時的なファイルを作成し、成功したら名前を変更します。

set -e 
psql $PG_HOST -At -F$'\t' -c \
    "SELECT * FROM mytable"  >  /tmp/mytable.txt~
                          # ^^^ cf. Useless Use of Cat
mv /tmp/mytable.txt~ /tmp/mytable.txt

私はいつも作るこの種の場合には、エラー時に停止し、再開可能なパイプラインを構築できるためです。

関連情報