一段檢查或處理資料的指令,用豎線接到下一段後,前段明明失敗,整條管線卻回傳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