如何在 Linux 上的 Bash 腳本中使用 set 和 pipefail
已發表: 2022-06-25 Linux set
和pipefail
命令指示 Bash 腳本中發生故障時會發生什麼。 除了它應該停止還是應該繼續之外,還有更多需要考慮的事情。
相關: Shell 腳本初學者指南:基礎
Bash 腳本和錯誤條件
Bash shell 腳本很棒。 它們寫得很快,而且不需要編譯。 您需要執行的任何重複或多階段操作都可以包含在一個方便的腳本中。 而且由於腳本可以調用任何標準的 Linux 實用程序,因此您不受限於 shell 語言本身的功能。
但是當您調用外部實用程序或程序時可能會出現問題。 如果失敗,外部實用程序將關閉並向 shell 發送返回碼,甚至可能向終端打印錯誤消息。 但是您的腳本將繼續處理。 也許那不是你想要的。 如果在腳本執行的早期發生錯誤,如果允許腳本的其餘部分運行,則可能會導致更嚴重的問題。
您可以在每個外部進程完成時檢查它們的返回碼,但是當進程通過管道傳輸到其他進程時,這變得很困難。 返回碼將來自管道末端的進程,而不是中間失敗的進程。 當然,腳本內部也可能發生錯誤,例如嘗試訪問未初始化的變量。
set
和pipefile
命令讓您決定發生此類錯誤時會發生什麼。 即使錯誤發生在管道鏈的中間,它們也可以讓您檢測到錯誤。
以下是如何使用它們。
展示問題
這是一個簡單的 Bash 腳本。 它將兩行文本回顯到終端。 如果將文本複製到編輯器中並將其保存為“script-1.sh”,則可以運行此腳本。
#!/bin/bash echo 這將首先發生 echo 這將發生在第二個
要使其可執行,您需要使用chmod
:
chmod +x script-1.sh
如果要在計算機上運行它們,則需要在每個腳本上運行該命令。 讓我們運行腳本:
./script-1.sh
兩行文本按預期發送到終端窗口。
讓我們稍微修改一下腳本。 我們將要求ls
列出不存在的文件的詳細信息。 這將失敗。 我們將其保存為“script-2.sh”並使其可執行。
#!/bin/bash echo 這將首先發生 ls 虛構文件名 echo 這將發生在第二個
當我們運行這個腳本時,我們會看到來自ls
的錯誤消息。
./script-2.sh
雖然ls
命令失敗,但腳本繼續運行。 即使在腳本執行過程中出現錯誤,從腳本到 shell 的返回碼為零,這表明成功。 我們可以使用 echo 和$?
保存發送到 shell 的最後一個返回碼的變量。
迴聲$?
報告的零是腳本中第二個回顯的返回碼。 所以這個場景有兩個問題。 首先是腳本有錯誤但它繼續運行。 如果腳本的其餘部分期望或依賴於失敗的操作實際成功,這可能會導致其他問題。 第二個是如果另一個腳本或進程需要檢查這個腳本的成功或失敗,它會得到一個錯誤的讀數。
set -e 選項
如果腳本調用的任何進程生成非零返回碼, set -e
(退出)選項會導致腳本退出。 任何非零都被視為失敗。
通過在腳本的開頭添加set -e
選項,我們可以改變它的行為。 這是“script-3.sh”。
#!/bin/bash 設置-e echo 這將首先發生 ls 虛構文件名 echo 這將發生在第二個
如果我們運行這個腳本,我們將看到set -e
的效果。
./script-3.sh
迴聲$?
腳本停止並且發送到 shell 的返回碼是一個非零值。
處理管道故障
管道增加了問題的複雜性。 來自管道命令序列的返回碼是鏈中最後一個命令的返回碼。 如果鏈條中間的命令出現故障,我們將回到第一方。 該返回碼丟失,腳本將繼續處理。
我們可以使用true
和false
shell 內置函數查看具有不同返回碼的管道命令的效果。 這兩個命令只不過分別生成一個零或一的返回碼。
真的
迴聲$?
錯誤的
迴聲$?
如果我們通過管道將false
轉換為true
(使用false
表示失敗的進程),我們會得到true
的返回碼為零。
假 | 真的
迴聲$?
Bash 確實有一個名為PIPESTATUS
的數組變量,它捕獲了管道鏈中每個程序的所有返回代碼。
假 | 真實 | 假 | 真的
echo "${PIPESTATUS[0]} ${PIPESTATUS[1]} ${PIPESTATUS[2]} ${PIPESTATUS[3]}"
PIPESTATUS
只保留返回代碼,直到下一個程序運行,並且試圖確定哪個返回代碼與哪個程序一起使用會很快變得非常混亂。
這就是set -o
(選項)和pipefail
的用武之地。這是“script-4.sh”。 這將嘗試將不存在的文件的內容通過管道傳輸到wc
中。
#!/bin/bash 設置-e echo 這將首先發生 貓腳本-99.sh | wc -l echo 這將發生在第二個
正如我們所料,這失敗了。
./script-4.sh
迴聲$?
第一個零是wc
的輸出,告訴我們它沒有讀取丟失文件的任何行。 第二個零是第二個echo
命令的返回碼。
我們將添加-o pipefail
,將其保存為“script-5.sh”,並使其可執行。
#!/bin/bash 設置-eo pipefail echo 這將首先發生 貓腳本-99.sh | wc -l echo 這將發生在第二個
讓我們運行它並檢查返回碼。
./script-5.sh
迴聲$?
腳本停止,第二個echo
命令沒有執行。 發送到 shell 的返回碼是 1,正確指示失敗。
相關:如何在 Linux 上使用 Echo 命令
捕獲未初始化的變量
未初始化的變量很難在真實腳本中被發現。 如果我們嘗試echo
顯未初始化變量的值, echo
顯只會打印一個空行。 它不會引發錯誤消息。 腳本的其餘部分將繼續執行。
這是腳本 6.sh。
#!/bin/bash 設置-eo pipefail 迴聲“$notset” echo "另一個回顯命令"
我們將運行它並觀察它的行為。
./script-6.sh
迴聲$?
腳本會跳過未初始化的變量,並繼續執行。 返回碼為零。 試圖在一個非常長而復雜的腳本中找到這樣的錯誤可能非常困難。
我們可以使用set -u
(unset) 選項來捕獲此類錯誤。 我們將把它添加到腳本頂部不斷增長的設置選項集合中,將其保存為“script-7.sh”,並使其可執行。
#!/bin/bash 設置-eou pipefail 迴聲“$notset” echo "另一個回顯命令"
讓我們運行腳本:
./script-7.sh
迴聲$?
檢測到未初始化的變量,腳本停止,返回碼設置為 1。
-u
(未設置)選項足夠智能,不會被您可以合法地與未初始化變量交互的情況觸發。
在“script-8.sh”中,腳本檢查變量New_Var
是否已初始化。 您不希望腳本停在這裡,在真實世界的腳本中,您將執行進一步的處理並自己處理情況。
請注意,我們在 set 語句中添加了-u
選項作為第二個選項。 -o pipefail
選項必須放在最後。
#!/bin/bash 設置 -eu 管道故障 如果 [ -z "${New_Var:-}" ]; 然後 echo "New_Var 沒有賦值。" 菲
在“script-9.sh”中,測試未初始化的變量,如果未初始化,則提供默認值。
#!/bin/bash 設置 -eu 管道故障 默認值=484 值=${New_Var:-$default_value} 迴聲“New_Var=$Value”
允許腳本運行到完成。
./script-8.sh
./script-9.sh
用斧頭密封
另一個方便使用的選項是set -x
(執行和打印)選項。 當您編寫腳本時,這可以成為救命稻草。 它在執行命令時打印命令及其參數。
它為您提供了一種快速“粗略且準備就緒”的執行跟踪形式。 隔離邏輯缺陷和發現錯誤變得非常容易。
我們將 set -x 選項添加到“script-8.sh”,將其保存為“script-10.sh”,並使其可執行。
#!/bin/bash 設置-euxo pipefail 如果 [ -z "${New_Var:-}" ]; 然後 echo "New_Var 沒有賦值。" 菲
運行它以查看跟踪線。
./script-10.sh
在這些簡單的示例腳本中發現錯誤很容易。 當您開始編寫更多涉及的腳本時,這些選項將證明它們的價值。