使用 set -eu
時 EXIT 和 ERR 陷阱的正確行為
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
- 擴展錯誤是在執行Word Expansions中定義的 shell 擴展時發生的錯誤(例如
"${x!y}"
,因為!
不是有效的運算符);如果實現可以在標記化期間而不是在擴展期間檢測到它們,則可以將它們視為語法錯誤。- $$ A $$n 互動式 shell 應在不退出的情況下將診斷消息寫入標準錯誤。
也來自
man bash
:
trap ... ERR
- 如果 sigspec 為ERR ,則只要管道*(可能由單個簡單命令組成)、列表或複合命令返回非零退出狀態, 就會執行命令arg ,但需滿足以下條件:*
- 如果失敗的命令是緊跟在or關鍵字之後的命令列表的一部分,則不會執行ERR陷阱…
while``until
- …聲明中的部分測試
if
…- …在一個
&&
或||
列表中執行的命令的一部分,除了最後一個&&
或||
…之後的命令- …管道中的任何命令,但最後一個…
- …或者如果命令的返回值正在使用
!
.- 這些與errexit
-e
選項遵循的條件相同。請注意,ERR陷阱完全是關於評估其他命令的返回。但是當發生擴展錯誤時,沒有命令執行返回任何內容。在您的範例中,
echo
永遠不會發生- 因為當 shell 評估和擴展其參數時,它會遇到一個-u
nset 變數,該變數已由顯式 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
行號的錯誤。- 修復了導致SIGINT和SIGQUIT無法
trap
在非同步子 shell 命令中執行的錯誤。- 修復了導致第二個和後續SIGINT被互動式 shell 忽略的中斷處理問題。
- shell 在為這些信號執行處理程序時不再阻止接收
trap
信號,並允許大多數trap
處理程序遞歸 執行(在處理程序執行時執行trap
處理trap
程序)。我認為它要麼是最後一個,要麼是第一個最相關的——或者可能是兩者的結合。
trap
處理程序本質上是非同步的,因為它的全部工作就是等待和處理非同步信號。你用-eu
和同時觸發兩個$UNSET_VAR
。所以也許你應該只更新,但如果你喜歡自己,你會用完全不同的外殼來做。