Bash前段指令失敗卻回傳0:用pipefail檢查管線

一段檢查或處理資料的指令,用豎線接到下一段後,前段明明失敗,整條管線卻回傳0。若你用這個結果決定是否繼續備份、部署或匯入,可能漏掉前面的錯誤。Bash一般以管線最後一段的退出狀態作為整體結果;啟用pipefail後,才會把前段的非零狀態納入判斷。

先重現:前段失敗,最後一段仍成功

以下用false刻意回傳1、true回傳0,方便觀察規則,不會處理檔案。請存成.sh檔並用bash 檔名.sh執行;範例使用Bash,不能假設所有以sh執行的環境都有相同選項。

#!/usr/bin/env bash
set +o pipefail
false | true
rc=$?
printf 'pipeline status=%s\n' "$rc"
# pipeline status=0

set -o pipefail
false | true
rc=$?
printf 'pipeline status=%s\n' "$rc"
# pipeline status=1

第一組關閉pipefail,管線狀態取自最後的true,所以是0。第二組啟用後,前段false的1成為整條管線的狀態。若所有段落都回傳0,啟用pipefail後的整體結果仍是0。

rc=$?要緊接管線。若先執行echo或另一條指令,$?就會變成那條指令的狀態;上面的printf是在保存rc之後才顯示結果。

多段都失敗時,取最右側的非零狀態

#!/usr/bin/env bash
set -o pipefail
(exit 7) | (exit 3) | true
rc=$?
printf 'pipeline status=%s\n' "$rc"
# pipeline status=3

結果是3,不是7,也不是最後一段true的0。pipefail取管線位置最右側、且退出狀態非零的那一段;不是取最大的數字,也不是取最後完成執行的程序。括號裡的exit只結束該子shell,不會直接結束外層腳本。

取得失敗狀態後,還要決定下一步

pipefail本身不等於「出錯立即停止」。前面的第二組範例中,管線回傳1後,printf仍會執行。若這個步驟失敗就必須停止,請明確寫出分支,並保留原來的狀態。

#!/usr/bin/env bash
set -o pipefail

if false | true; then
    printf '檢查完成,繼續下一步\n'
else
    rc=$?
    printf '管線未通過,status=%s\n' "$rc" >&2
    exit "$rc"
fi

這段會走else,顯示status=1,並讓腳本以1結束。換成自己的指令前,要先確認各工具的退出碼定義:有些非零狀態代表「沒有符合資料」等預期情況,不能只看數字就判斷資料損壞。

套進排程前,先核對這三件事

  • 排程實際執行的是Bash;腳本第一行寫了Bash,卻用sh 檔名.sh啟動,仍可能跑錯shell。
  • 管線後立即保存或判斷狀態,失敗分支確實阻止不應繼續的步驟。
  • 以無破壞性的測試資料分別跑成功與失敗路徑。非零狀態不會替你撤銷已完成的寫入,也不保證前面產生的資料完整。

先用false與true核對退出碼,再換成自己的處理流程。這樣可以分清楚:是工具本身出了問題,還是管線最後一段的成功把前面的失敗蓋住。

參考資料

原文鏈接:https://wntheme.com/bash-pipeline-exit-status-pipefail/,轉載請註明出處。
0

評論0

顯示驗證碼
沒有帳號?註冊  忘記密碼?