
errexit ( set -e
) 通常被建議作為一種使簡單腳本更加健壯的方法。然而,任何見過它在更複雜的腳本(尤其是函數)中的行為的人通常都會認為它是一個可怕的陷阱。子 shell 也有非常相似的問題。舉個例子,改編自https://stackoverflow.com/questions/29926013/exit-subshell-on-error
(
set -o errexit
false
true
) && echo "OK" || echo "FAILED";
這裡的陷阱是 shell 顯示“OK”,而不是“FAILED”。
雖然在特定情況下的行為set -e
在歷史上以不幸的方式有所不同,但現代發行版上可用的 shell 聲稱遵循 POSIX shell 標準,並且測試顯示以下所有項目都有相同的行為(“OK”):
bash-4.4.19-2.fc28.x86_64
(軟呢帽)busybox-1.26.2-3.fc27.x86_64
(軟呢帽)dash 0.5.8-2.4
(Debian 9)zsh 5.3.1-4+b2
(Debian 9)posh 0.12.6+b1
(Debian 9)ksh 93u+20120801-3.1
(Debian 9)。
問題
是否有任何技術原因導致您無法編寫具有與 POSIX 類似功能的 shell
sh
,除了它列印FAILED
上述程式碼之外?我希望它也能改變,以便傳回 false 的
set -e
頂級表達式被認為是致命的。&&
https://serverfault.com/a/847016/133475 希望它能有一些更好的習慣用法與 grep 一起使用。set -e # print matching lines, but it's not an error if there are no matches in the file ( set +e grep pattern file.txt RET=$? [[ $RET == 0 || $RET == 1 ]] || exit $RET )
我想我還假設算術語句(
let
內建)將以某種方式重新定義,以避免隱式地將其數值強制為真/假退出狀態。有沒有一個 shell 的範例可以做到這一點?
我不介意查看與 Bourne 語法不同的內容。在編寫簡短的“粘合”腳本時,我對緊湊的東西感興趣,但它也有一個緊湊的策略來檢測錯誤。我不喜歡用作
&&
語句分隔符,如果這意味著意外省略語句分隔符將導致錯誤被默默忽略。
[*] 編輯。這也許不是一個完美的例子。討論顯示更好的例子是將set -o errexit
子 shell 移到上面。
(false; echo foo) || echo bar; echo \ $?
AFAICT 這與表中的測試案例非常相似這裡,它表示它將在原始 Bourne shell 及其大多數後代上顯示“foo”(然後是“0”)。例外情況是「hist.ash」和 bash 1.14.7。
當您set -e
在子 shell 中新增 - (set -e; false; echo foo) || echo bar; echo \ $?
- 時,有兩個額外的例外,「SVR4 sh sun5.10」和 dash-0.3.4。
就我的問題的精神而言,我認為進入set -e
子外殼會分散注意力。我主要感興趣的是在set -e
腳本頂部使用的習慣用法,它也適用於子 shell(這已經是 POSIX 行為)。
Jörg Schilling 表示,對於我在這個問題中使用的範例,原始 Unix 中的 Bourne shell 會列印“FAILED”,他已將此 shell 移植到 POSIX,如下所示希利工具中的“osh”,並且該結果已於 2018 年 6 月 11 日發佈時得到驗證。也許這是基於 1)set -e
位於子 shell 內部和 2) “SVR4 sh sun5.10”。 「osh」「基於 OpenSolaris 來源,因此基於 SVR4 和 SVID3」。或者也許在&& echo "OK"
subshell 和 之間添加會導致一些可怕的額外差異|| echo "FAILED"
。
我認為狡猾的外殼不能回答我的問題。您可以對函數使用子 shell(使用(
代替{
在子 shell 中運行該函數),並以 . 啟動每個子 shell set -e
。但是,使用子 shell 會帶來無法修改全域變數的限制。至少,我還沒有看到有人捍衛它作為通用編碼風格。
答案1
這POSIX標準(參見-e)比 Bash 手冊中有關errexit
shell 選項的內容更清楚。
啟用此選項後,當任何命令失敗時(由於 Shell 錯誤的後果中列出的任何原因或返回大於零的退出狀態),shell 應立即退出,就像執行 exit 特殊內置實用程式一樣不帶任何參數,但以下情況除外:
多命令管道中任何單一命令的失敗都不應導致 shell 退出。僅考慮管道本身的故障。
當執行 while、until、if 或 elif 保留字(以 ! 開頭的管道)後面的複合清單時,應忽略 -e 設定。保留字,或 AND-OR 清單中除最後一個以外的任何命令。
如果子 shell 指令以外的複合指令的退出狀態是在 -e 被忽略時失敗的結果,則 -e 不適用於該指令。
此要求分別適用於 shell 環境和每個子 shell 環境。例如,在:
set -e; (false; echo one) | cat; echo two
false 指令導致子 shell 退出而不執行 echo one;然而,執行 echo 2 是因為管道的退出狀態 (false; echo one) |貓為零。
顯然,範例程式碼等效於以下偽代碼。
( LIST; ) && COMMAND || COMMAND
的退出狀態清單 為零,因為:
shell
errexit
選項被忽略。返回狀態是指定的最後一個命令的退出狀態清單, 這裡
true
。
因此,執行 AND 清單的第二部分:echo "OK"
。
答案2
不是「有人嘗試過寫…」這個問題的答案,而是關於如何完成它的一些想法。好奇是否有人可以提供回饋。
目前腳本解決方案(Python、JavaScript 和較舊的 Perl 等)中的常見方法是使用 try ... catch 區塊。預期行為是任何錯誤都會觸發異常。此行為與 $? 中捕獲錯誤的 *sh 行為衝突。顯然,「-e」未能以大多數開發人員期望的方式正確工作。無需在這裡詳細說明,因為這已經被廣泛記錄。
兩種可能的方法:
- 創建更好的
set -e
:例如,set -ocatcherr
具有更好的錯誤處理能力。 - 新增
try { list ; }
指令,遵循現代腳本解決方案
'-ofailerr` 可以定義如下:
- 如果任何命令在控制流之外失敗(if/while/until),則當前執行上下文應傳回 1(如果這是頂級區塊,則退出)。此變更將有效地迫使開發人員對任何失敗的命令提供錯誤處理,或明確標記帶有可以忽略的錯誤的命令。
- 適用於簡單的腳本,有效地在未處理的錯誤上中止。
方法try { list ; }
類似,只是語法不同
例子:
function foo {
cat $* > a.txt
wc -l a.txt
}
set -oerrfail
foo /non/existing/file # Will fail, without executing the "wc -l"
if foo /non/existing/file ; then
...
else
# This is the "catch" block
fi
使用 try 區塊:
try {
foo /non/existing/file
...
} || { catch-block }
實際上,該 try 相當於“set -oerrofail”,在“if !”下運行命令列表。 { ... } ;然後 { catch 區塊 }