如何在管道中第一個指令失敗時強制執行 set -o pipelinefail

如何在管道中第一個指令失敗時強制執行 set -o pipelinefail

我正在嘗試將資料從 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成功並產生非空輸出”和“consumer失敗且不產生輸出”,您可以使用ifne來自 Joey Hess 的 moreutils

producer | ifne consumer

我認為這在你的用例中不起作用——可能碰巧沒有匹配的行(誤報,你得到過時的數據),數據庫連接可能在中間丟失(誤報,你得到被截斷的數據) )。

如果您需要知道生產者是否成功,那麼您需要等到它完成後再啟動消費者。由於消費者尚未出現,因此需要儲存輸出。

如果輸出不包含空位元組、以一個且僅一個換行符號結尾且不是太大,則可以將其儲存在 shell 變數中。

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 和其他一些 shell(包括 ksh93 和 bash)中,最後一行可以簡化為consumer <<<"$output".

請注意,命令替換會刪除尾隨換行符號。如果尾隨空行相關,解決方法是將第一行變更為

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

$output然後將包含完整的輸出,包括尾隨換行符(如果有)。然後使用printf %s "$output"而不是printf '%s\n' "$output"將其提供給consumer.

如果輸出可能太大或可能包含空字節,請將其儲存在臨時檔案中。

答案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

我總是用製作對於這類事情,因為它會在錯誤時停止並讓我建立可重新啟動的管道。

相關內容