跟其他高級語言一樣,Bash/Shell腳本也可以進行調試(Debug),學會這些調試技巧對編寫和完善腳本有不小的幫助。
在本文中,您將學習一些有用的Bash/Shell腳本調試方法:
- 如何使用傳統技術
- 如何使用xtrace選項
- 如何使用其他Bash選項
- 如何使用trap
使用的軟件要求和約定
類別 | 使用的要求,約定或軟件版本 |
---|---|
係統 | 任何GNU /Linux發行版 |
軟件 | GNU Bash |
其他 | N/A |
約定 | #-要求linux命令可以直接以root用戶身份或通過使用root特權以root特權執行sudo 命令
$-要求linux命令以普通非特權用戶身份執行 |
使用傳統技術
即使代碼錯誤很明顯,調試代碼也可能很棘手。程序員傳統上利用諸如調試器和編輯器中的語法突出顯示之類的工具來協助解決問題。編寫Bash腳本沒有什麽不同。隻需突出顯示語法即可使您在編寫代碼時捕獲錯誤。
一些編程語言附帶了調試功能,例如gcc和gdb,它們使您可以逐步執行代碼,設置斷點,在執行時檢查這些點處的所有內容的狀態,等等。但是,Bash腳本通常不需要像這樣的笨拙方法。 對於Shell腳本來說,僅對代碼進行解釋,而不是將其編譯為二進製文件。
傳統編程環境中使用了一些對複雜的Bash腳本有用的技術,例如使用斷言。即在某個時間點明確聲明條件或事物狀態的方式,它們可以實現為一個簡短的函數,該函數可以顯示時間,行號等,或類似如下代碼所示的東西:
$ echo "function_name(): value of \$var is ${var}"
如何使用Bash xtrace選項
在編寫Shell腳本時,編程邏輯往往會更短,並且通常包含在單個文件中。因此,我們可以使用一些內置調試選項來查看出了什麽問題。要提到的第一個選項可能也是最有用的是-xtrace
選項。可以通過使用以下命令調用Bash將其應用於腳本-x
開關。
$ bash -x <scriptname>
-x及與其
相反的-v
,-v顯示每行之前的值,而不是之後的值。這兩個選項可以組合使用,也就是同時使用-x
和-v,從而
看到變量替換發生前後的語句。
如何使用其他Bash選項
默認情況下,用於調試的Bash選項處於關閉狀態,但是一旦使用set命令將其打開,它們將保持打開狀態,直到顯式關閉為止。如果您不確定啟用了哪些選項,則可以檢查$-
變量以查看所有變量的當前狀態。
$ echo $-
himBHs
$ set -xv && echo $-
himvxBHs
我們可以使用另一個有用的開關來幫助我們找到引用的變量,而無需設置任何值。這是-u開關
,就像-x
和-v
也可以在命令行中使用它,如下麵的示例所示:
我們錯誤地為名為“level”的變量賦予了7這個值,然後試圖回顯名為“score”的變量,該未定義變量僅導致屏幕完全不打印任何內容。設置-u
開關使我們可以查看特定的錯誤消息“score: unbound variable”,該錯誤消息確切指示出了問題所在。
我們可以在簡短的Bash腳本中使用這些選項,以向我們提供調試信息,以識別那些不會觸發Bash解釋器反饋的問題。讓我們來看幾個例子。
#!/bin/bash
read -p "Path to be added: " $path
if [ "$path" = "/home/mike/bin" ]; then
echo $path >> $PATH
echo "new path: $PATH"
else
echo "did not modify PATH"
fi
在上麵的示例中,我們正常運行addpath腳本,但它根本不會修改我們的PATH
。執行時沒有給我們任何錯誤提示。使用-x選項再次運行它時清楚地表明,我們比較的左側是一個空字符串。$path
是一個空字符串,因為我們在讀取語句中不小心在”path”前麵加了一個美元符號。
看下一個示例,我們也沒有從解釋器得到任何錯誤指示。每行僅打印一個值,而不是兩個。這是不會導致腳本停止執行的錯誤。使用-u開光
,我們立即收到通知,我們的變量j
沒有綁定到值。因此,當我們收到從Bash解釋器的角度來看不會導致實際錯誤的錯誤時,這能幫我們節省時間。
#!/bin/bash
for i in 1 2 3
do
echo $i $j
done
當我們處理更長和更複雜的腳本時,設置-xv
選項,然後運行更複雜的腳本,除了會使生成的輸出量增加一倍或兩倍,而且需要為多個bash命令行修改代碼增加該選項,從而增加混亂。
幸運的是,我們可以通過將它們放置在腳本中來更精確地使用這些選項。我們可以通過將選項添加到shebang行中來設置選項,而不是通過命令行顯式調用Bash shell。
#!/bin/bash -x
這將為整個文件設置-x
選項(或者直到腳本執行期間未設置它為止),允許您通過鍵入文件名而不是將其作為參數傳遞給Bash命令來簡單地運行腳本。但是,使用這種技術,長腳本或具有大量輸出的腳本仍然內容多且混亂,因此,讓我們看一下使用選項的更具體方法。
對於更具針對性的方法,請僅用可選項包圍可疑代碼塊。此方法可以通過分別使用帶有正負的set關鍵字來實現。
#!/bin/bash
read -p "Path to be added: " $path
set -xv
if [ "$path" = "/home/mike/bin" ]; then
echo $path >> $PATH
echo "new path: $PATH"
else
echo "did not modify PATH"
fi
set +xv
我們僅圍繞我們懷疑的代碼塊以減少輸出,從而使我們的任務在調試過程中更加容易查看。請注意,我們僅對包含if-then-else語句的代碼塊打開選項,然後在可疑塊的末尾關閉選項。如果我們無法縮小可疑區域的範圍,或者想要在腳本執行過程中的各個時間點評估變量的狀態,則可以在一個腳本中多次打開和關閉這些選項。如果我們希望在腳本執行的其餘部分繼續執行該選項,則無需關閉該選項。
我們還應該提到,有第三方編寫的調試器,這些調試器使我們可以逐行逐步執行代碼。您可能想研究這些工具,但是大多數人發現它們實際上並不是必需的。
正如經驗豐富的程序員所建議的那樣,如果您的代碼太複雜而無法使用這些選項隔離可疑塊,那麽真正的問題是應該重構代碼。過於複雜的代碼意味著可能很難檢測到錯誤,並且維護費時費力。
關於Bash調試選項,最後要提到的一件事是,還存在文件globing選項:-f
。啟用此選項後,設置它會關閉globing(擴展通配符以生成文件名)。
#!/bin/bash
echo "ignore fileglobbing option turned off"
ls *
echo "ignore file globbing option set"
set -f
ls *
set +f
如何使用trap幫助調試
如果腳本複雜,還有更多值得考慮的技術,包括使用前麵提到的assert函數。另外一種方法是使用trap。 Shell腳本使我們可以捕獲(trap)信號並在此時執行某些操作。
您可以在Bash腳本中使用的一個簡單但有用的trap示例是捕獲EXIT
。
#!/bin/bash
trap 'echo score is $score, status is $status' EXIT
if [ -z ]; then
status="default"
else
status=
fi
score=0
if [ ${USER} = 'superman' ]; then
score=99
elif [ $# -gt 1 ]; then
score=
fi
如您所見,僅將變量的當前值輸出到屏幕對於顯示邏輯失敗的位置很有用。EXIT
信號顯然不需要顯式exit
產生的陳述;在這種情況下echo
到達腳本末尾時執行語句。
與Bash腳本一起使用的另一個有用的trap是DEBUG
。這是在每個語句之後發生的,因此可以用作蠻力方式在腳本執行的每個步驟中顯示變量的值。
#!/bin/bash
trap 'echo "line ${LINENO}: score is $score"' DEBUG
score=0
if [ "${USER}" = "mike" ]; then
let "score += 1"
fi
let "score += 1"
if [ "" = "7" ]; then
score=7
fi
exit 0
結論
當您發現您的Bash腳本未按預期方式運行,並且難以確定問題的原因時,請考慮哪些信息將有助於您確定原因,然後使用最方便的調試工具來幫助您確定問題。