Bash

使用 set -eu 時 EXIT 和 ERR 陷阱的正確行為

  • November 17, 2021

set -e在使用( errexit)、set -u( nounset) 以及 ERR 和 EXIT 陷阱時,我觀察到一些奇怪的行為。它們似乎是相關的,因此將它們放在一個問題上似乎是合理的。

1)set -u不觸發 ERR 陷阱

  • 程式碼:
#!/bin/bash
trap 'echo "ERR (rc: $?)"' ERR
set -u
echo ${UNSET_VAR}
  • 預期:ERR 陷阱被呼叫,RC != 0
  • 實際:沒有呼叫 ERR 陷阱,RC == 1
  • 注意:set -e不改變結果

2)set -eu在 EXIT 陷阱中使用退出程式碼是 0 而不是 1

  • 程式碼:
#!/bin/bash
trap 'echo "EXIT (rc: $?)"' EXIT
set -eu
echo ${UNSET_VAR}
  • 預期:EXIT 陷阱被呼叫,RC == 1
  • 實際:呼叫了 EXIT 陷阱,RC == 0
  • 注意:使用set +e時,RC == 1。當任何其他命令拋出錯誤時,EXIT 陷阱會返回正確的 RC。
  • **編輯:**關於這個主題有一個 SO 文章,其中有一個有趣的評論,暗示這可能與正在使用的 Bash 版本有關。使用 Bash 4.3.11 測試此程式碼段會導致 RC=1,這樣會更好。不幸的是,目前無法在所有主機上升級 Bash(從 3.2.51 開始),所以我們必須想出一些其他的解決方案。

誰能解釋這些行為中的任何一種?

搜尋這些主題並不是很成功,考慮到關於 Bash 設置和陷阱的文章數量,這相當令人驚訝。雖然有一個論壇主題,但結論相當不令人滿意。

來自man bash

  • set -u
    • "@"在執行參數擴展時,將未設置的變數和特殊參數以外的參數"*"視為錯誤。如果嘗試對未設置的變數或參數進行擴展,shell 會列印一條錯誤消息,如果不是-i互動式的,則以非零狀態退出。

POSIX 聲明,在擴展錯誤的情況下,當擴展與 shell 特殊內置函式(這是一個經常忽略的區別**,因此可能無關緊要)或除此之外的任何其他實用程序相關聯時,非互動式 shell將退出.bash

也來自man bash

  • trap ... ERR
    • 如果 sigspec 為ERR ,則只要管道*(可能由單個簡單命令組成)、列表或複合命令返回非零退出狀態, 就會執行命令arg ,但需滿足以下條件:*
      • 如果失敗的命令是緊跟在or關鍵字之後的命令列表的一部分,則不會執行ERR陷阱…while``until
      • …聲明中的部分測試if
      • …在一個&&||列表中執行的命令的一部分,除了最後一個&&||…之後的命令
      • …管道中的任何命令,但最後一個…
      • …或者如果命令的返回值正在使用!.
    • 這些與errexit -e選項遵循的條件相同。

請注意,ERR陷阱完全是關於評估其他命令的返回。但是當發生擴展錯誤時,沒有命令執行返回任何內容。在您的範例中,echo 永遠不會發生- 因為當 shell 評估和擴展其參數時,它會遇到一個-unset 變數,該變數已由顯式 shell 選項指定,以導致立即退出目前的腳本 shell。

因此EXIT陷阱(如果有)被執行,並且 shell 以診斷消息和退出狀態而不是 0 退出 - 完全按照它應該做的那樣。

至於rc: 0的事情,我希望這是某種特定於版本的錯誤 - 可能與EXIT的兩個觸發器同時發生和一個獲得另一個的退出程式碼*(不應該發生)*有關。無論如何,使用由以下人員安裝的最新bash二進製文件pacman

bash <<\IN
   printf "shell options:\t$-\n"
   trap 'echo "EXIT (rc: $?)"' EXIT
   set -eu
   echo ${UNSET_VAR}
IN

我添加了第一行,因此您可以看到 shell 的條件是腳本 shell 的條件 - 它不是互動式的。輸出是:

shell options:  hB
bash: line 4: UNSET_VAR: unbound variable
EXIT (rc: 1)

以下是最近變更日誌中的一些相關說明:

  • 修復了導致非同步命令設置不$?正確的錯誤。
  • 修復了導致命令中的擴展錯誤生成的錯誤消息具有 錯誤for行號的錯誤。
  • 修復了導致SIGINTSIGQUIT無法trap在非同步子 shell 命令中執行的錯誤。
  • 修復了導致第二個和後續SIGINT被互動式 shell 忽略的中斷處理問題。
  • shell 在為這些信號執行處理程序時不再阻止接收trap信號,並允許大多數 trap處理程序遞歸 執行(在處理程序執行時執行trap處理trap程序)

我認為它要麼是最後一個,要麼是第一個最相關的——或者可能是兩者的結合。trap處理程序本質上是非同步的,因為它的全部工作就是等待和處理非同步信號。你用-eu和同時觸發兩個$UNSET_VAR

所以也許你應該只更新,但如果你喜歡自己,你會用完全不同的外殼來做。

引用自:https://unix.stackexchange.com/questions/208112