如何在 Linux 上捕獲 Bash 腳本中的錯誤

已發表: 2022-08-27
顯示 bash 提示符的 Linux 筆記本電腦
fatmawati achmad zaenuri/Shutterstock.com

默認情況下,Linux 上的 Bash 腳本會報告錯誤但會繼續運行。 我們將向您展示如何自己處理錯誤,以便您決定接下來需要發生什麼。

腳本中的錯誤處理

處理錯誤是編程的一部分。 即使您編寫了完美的代碼,您仍然可能會遇到錯誤情況。 隨著您安裝和卸載軟件、創建目錄以及執行升級和更新,計算機上的環境會隨著時間而變化。

在 PowerShell 中自定義參數驗證錯誤
相關在 PowerShell 中自定義參數驗證錯誤

例如,如果目錄路徑更改或文件的權限發生更改,以前可以正常運行的腳本可能會遇到困難。 Bash shell 的默認操作是打印錯誤消息並繼續執行腳本。 這是一個危險的默認值。

如果失敗的操作對腳本中稍後發生的某些其他處理或操作至關重要,則該關鍵操作將不會成功。 結果是多麼災難性,取決於您的腳本試圖做什麼。

更強大的方案將檢測錯誤並讓腳本在需要關閉或嘗試修復故障條件時運行。 例如,如果缺少目錄或文件,讓腳本重新創建它們可能會令人滿意。

如果腳本遇到無法恢復的問題,它可以關閉。 如果腳本必須關閉,它可以有機會執行任何需要的清理工作,例如刪除臨時文件或將錯誤條件和關閉原因寫入日誌文件。

檢測退出狀態

命令和程序會生成一個值,在它們終止時發送給操作系統。 這稱為他們的退出狀態。 如果沒有錯誤,則它的值為零,如果發生錯誤,則它具有一些非零值。

我們可以檢查腳本使用的命令的退出狀態(也稱為返回碼),並確定命令是否成功。

在 Bash 中,零等於真。 如果命令的響應不是真的,我們就知道發生了問題,我們可以採取適當的措施。

將此腳本複製到編輯器中,並將其保存到名為“bad_command.sh”的文件中。

 #!/bin/bash

如果(!bad_command); 然後
  echo "bad_command 標記了一個錯誤。"
  1號出口
菲

您需要使用chmod命令使腳本可執行。 這是使任何腳本可執行所需的步驟,因此如果您想在自己的機器上試用這些腳本,請記住對每個腳本都執行此操作。 在每種情況下替換相應腳本的名稱。

 chmod +x bad_command.sh 

使用 chmod 使腳本可執行

當我們運行腳​​本時,我們會看到預期的錯誤消息。

 ./bad_command.sh 

檢查命令的退出狀態以確定是否有錯誤

沒有像“bad_command”這樣的命令,也不是腳本中的函數名稱。 它無法執行,因此響應為零。 如果響應不為零(此處使用感嘆號作為邏輯NOT運算符),則執行if語句的主體。

在現實世界的腳本中,這可能會終止腳本,就像我們的示例所做的那樣,或者它可能會嘗試修復故障條件。

看起來exit 1線是多餘的。 畢竟,腳本中沒有其他內容,無論如何它都會終止。 但是使用exit命令可以讓我們將退出狀態傳回給 shell。 如果我們的腳本曾經在第二個腳本中被調用,那麼第二個腳本就會知道這個腳本遇到了錯誤。

您可以將邏輯OR運算符與命令的退出狀態一起使用,如果第一個命令有非零響應,則可以在腳本中調用另一個命令或函數。

 命令_1 || 命令_2

這是有效的,因為第一個命令運行OR第二個。 首先運行最左邊的命令。 如果成功,則不執行第二個命令。 但如果第一個命令失敗,則執行第二個命令。 所以我們可以像這樣構造代碼。 這是“邏輯或./sh”。

 #!/bin/bash

錯誤處理程序()
{
  迴聲“錯誤:($?)$ 1”
  1號出口
}

壞命令 || error_handler "bad_command 失敗,行:${LINENO}"

我們定義了一個名為error_handler的函數。 這將打印出失敗命令的退出狀態,保存在變量$? 以及在調用函數時傳遞給它的一行文本。 這保存在變量$1中。 該函數以退出狀態 1 終止腳本。

該腳本嘗試運行bad_command顯然失敗了,所以邏輯OR運算符右側的命令|| , 被執行。 這將調用error_handler函數並傳遞一個字符串,該字符串命名失敗的命令,並包含失敗命令的行號。

我們將運行腳本以查看錯誤處理程序消息,然後使用 echo 檢查腳本的退出狀態。

 ./邏輯或.sh
 迴聲$? 

使用邏輯 OR 運算符調用腳本中的錯誤處理程序

我們的小error_handler函數提供了嘗試運行bad_command的退出狀態、命令的名稱和行號。 在調試腳本時,這是有用的信息。

腳本的退出狀態為 1。 error_handler報告的 127 退出狀態表示“找不到命令”。 如果我們願意,我們可以通過將其傳遞給exit命令,將其用作腳本的退出狀態。

另一種方法是擴展error_handler以檢查退出狀態的不同可能值並相應地執行不同的操作,使用這種類型的構造:

 退出代碼=$?

如果 [ $exit_code -eq 1 ]; 然後
  echo "不允許操作"

elif [ $exit_code -eq 2 ]; 然後
  echo "濫用 shell 內置函數"
.
.
.
elif [ $status -eq 128 ]; 然後
  echo "參數無效"
菲

使用 set 強制退出

如果您知道希望腳本在出現錯誤時退出,您可以強制它這樣做。 這意味著您放棄了任何清理或任何進一步損壞的機會,因為您的腳本一旦檢測到錯誤就會終止。

為此,請使用帶有-e (錯誤)選項的set命令。 這告訴腳本在命令失敗或返回大於零的退出代碼時退出。 此外,使用-E選項可確保錯誤檢測和捕獲在 shell 函數中起作用。

如何在 Linux 上的 Bash 腳本中使用 set 和 pipefail
相關如何在 Linux 上的 Bash 腳本中使用 set 和 pipefail

要同時捕獲未初始化的變量,請添加-u (未設置)選項。 要確保在管道序列中檢測到錯誤,請添加-o pipefail選項。 沒有這個,管道命令序列的退出狀態就是序列中最後一個命令的退出狀態。 管道序列中間的失敗命令將不會被檢測到。 -o pipefail選項必須出現在選項列表中。

添加到腳本頂部的序列是:

 設置-Eeuo pipefail

這是一個名為“unset-var.sh”的簡短腳本,其中包含一個未設置的變量。

 #!/bin/bash

設置-Eeou pipefail

迴聲“$ unset_variable”

echo "我們看到這條線了嗎?"

當我們運行腳​​本時, unset_variable 被識別為一個未初始化的變量並且腳本被終止。

 ./unset-var.sh 

如果發生錯誤,在腳本中使用 set 命令終止腳本

第二個echo命令永遠不會執行。

使用有錯誤的陷阱

Bash 陷阱命令允許您指定一個命令或函數,當特定信號出現時應該調用它。 通常,這用於捕獲信號,例如當您按下 Ctrl+C 組合鍵時引發的SIGINT 。 這個腳本是“sigint.sh”。

 #!/bin/bash

陷阱 "echo -e '\n由 Ctrl+c' 終止;退出" SIGINT

計數器=0

雖然是真的
做 
  echo "循環編號:" $((++counter))
  睡覺 1
完畢

trap命令包含echo命令和exit命令。 當SIGINT被提升時,它將被觸發。 腳本的其餘部分是一個簡單的循環。 如果您運行腳本並按 Ctrl+C,您將看到來自trap定義的消息,並且腳本將終止。

 ./sigint.sh 

在腳本中使用陷阱捕獲 Ctrl+c

我們可以使用帶有ERR信號的trap來捕獲發生的錯誤。 然後可以將它們饋送到命令或函數。 這是“trap.sh”。 我們正在向一個名為error_handler的函數發送錯誤通知。

 #!/bin/bash

陷阱'error_handler $? $LINENO' 錯誤

錯誤處理程序(){
  echo "錯誤:($1) 發生在 $2"
}

主要的() {
  echo "main() 函數內部"
  壞命令
  第二
  第三
  退出 $?
}

第二() {
  echo "調用 main() 後"
  echo "在 second() 函數內"
}

第三() {
  echo "inside third() 函數"
}

主要的

大部分腳本在main函數內部,它調用secondthird函數。 當遇到錯誤時——在這種情況下,因為bad_command不存在—— trap語句將錯誤定向到error_handler函數。 它將失敗命令的退出狀態和行號傳遞給error_handler函數。

 ./trap.sh 

使用帶有 ERR 的陷阱來捕獲腳本中的錯誤

我們的error_handler函數只是將錯誤的詳細信息列出到終端窗口。 如果需要,可以向函數添加exit命令以終止腳本。 或者您可以使用一系列if/elif/fi語句來針對不同的錯誤執行不同的操作。

可能可以糾正一些錯誤,其他錯誤可能需要腳本停止。

最後的提示

捕獲錯誤通常意味著先發製人地發現可能出錯的事情,並在它們出現時放入代碼來處理這些不測事件。 這是除了確保腳本的執行流程和內部邏輯正確之外。

如果您使用此命令運行腳本,Bash 將在腳本執行時向您顯示跟踪輸出:

 bash -x 你的腳本.sh

Bash 在終端窗口中寫入跟踪輸出。 它顯示每個命令及其參數——如果有的話。 這發生在命令擴展之後但在它們執行之前。

它可以極大地幫助追踪難以捉摸的錯誤。

相關:如何在運行之前驗證 Linux Bash 腳本的語法