1. 程式人生 > >bash腳本測試總結

bash腳本測試總結

read nbsp 規範 條件表達式 space 模塊 你會 局限性 ech

bash腳本測試總結

跟蹤腳本的執行 可以讓bash打印出你腳本執行的過程中的所有語句。這很簡單,只需要使用bash的-x選項就可以做到,下面讓我們來看一下。 下面的這段腳本,先是輸出一個問候語句,然後輸出當前的時間: #!/bin/bash echo "Hello $USER," echo "Today is $(date +‘%Y-%m-%d‘)" 下面讓我們使用-x選項來運行這段腳本: $ bash -x example_script.sh + echo ‘Hello chenhao,‘ Hello chenhao, ++ date +%Y-%m-%d + echo ‘Today is 2009-08-31‘ Today is 2009-08-31 這時,我們可以看到,bash在運行前打印出了每一行命令。而且每行前面的+號表明了嵌套。 這樣的輸出可以讓你看到命令執行的順序並可以讓你知道整個腳本的行為。 在跟蹤裏輸出行號
在一個很大的腳本中,你會看到很多很多的執行跟蹤的輸出,閱讀起來非常費勁,所以,你可以在每一行前加上文件的行號,這會非常有用。要做到這樣,你只需要設置下面的環境變量: export PS4=‘+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: ‘ 讓我們看看設置上了PS4這個環境變量後會是什麽樣的輸出。 $ bash -x example_script.sh +example_script.sh:2:: echo ‘Hello chenhao,‘ Hello chenhao, ++example_script.sh:3:: date +%Y-%m-%d +example_script.sh:3:: echo ‘Today is 2009-08-31‘ Today is 2009-08-31 調試部份的腳本
有些時候,你並不想調試整個腳本,你只要調試其中的一部份。 那麽,你可以在你想要調試的腳本之前,調用“set -x”,結束的時候調用“set +x”就可以了。 如下面的腳本所示: #!/bin/bash echo "Hello $USER," set -x echo "Today is $(date %Y-%m-%d)" set +x 讓我們看看運行起來是啥樣? $ ./example_script.sh Hello chenhao, ++example_script.sh:4:: date +%Y-%m-%d +example_script.sh:4:: echo ‘Today is 2009-08-31‘ Today is 2009-08-31 +example_script.sh:5:: set +x 註意:我們在運行腳本的時候,不需要使用bash -x了。
日誌輸出 跟蹤日誌有時候太多了,多得都受不了,而且,輸出的內容很難閱讀。 一般來說,我們很多時候只關心於條件表達式,變量值,或是函數調用,或是循環等。。在這種情況下,log一些感興趣的特定的信息,可能會更好。 使用log前,我們先寫一個函數: _log() { if [ "$_DEBUG" == "true" ]; then echo 1>&2 "[email protected]" fi } 於是,你就可以在你的腳本中如下使用: _log "Copying files..." cp src/* dst/ 我們可以看到,上面那個_log函數,需要檢查一個_DEBUG 變量,只有這個變量是真,才會真正開發輸出日誌。這樣,你就只需要控制這個開關,而不需要刪除你的debug信息。 $ _DEBUG=true ./example_script.sh

BASH 的調試手段

Wen Pingbo 創作於 2015/06/06 評論 打賞

By WEN Pingbo of TinyLab.org 2015/06/01

平時在寫 BASH 腳本時,總是會碰到讓人抓狂的 BUG。和 C/C++ 這麽豐富的調試工具相比,BASH 又有什麽調試手段呢?

1 echo/print (普通技)

打印一些變量,或者提示信息。這應該是一個通用的方法了。在 BASH 裏,我們可以簡單的用 echo,或者 print 來輸出一些 log,或者加一些 loglevel 來過濾一些 log。這裏貼一下我平常用的函數:

_loglevel=2 DIE() { echo "Critical: $1" >&2 exit 1 } INFO() { [ $_loglevel -ge 2 ] && echo "INFO: $1" >&2 } ERROR() { [ $_loglevel -ge 1 ] && echo "ERROR: $1" >&2 }

這裏的實現只是簡單的加了一個 loglevel,其實可以把 log 輸出到一個文件中,或者給 log 加上顏色。比如:

# add color [ $_loglevel -ge 1 ] && echo -e "\033[31m ERROR:\033[0m $1" >&2 # redirect to file [ $_loglevel -ge 1 ] && echo "ERROR: $1" > /var/log/xxx_log.$BASHPID

2 set -x (稀有技)

-x(xtrace) 選項會導致 BASH 在執行命令之前,先把要執行的命令打印出來。這個選項對調試一些命令錯誤很有幫助。

有的時候,由於傳進來的參數帶有一些特殊字符,導致 BASH 解析時不是按照我們預想的進行。這個時候,把 -x 打開,就能在命令執行前,把擴展後的命令打印出來。比如基於前面寫的函數:

set -x INFO "this is a info log" ERROR "this is a error log" set +x

然後就可以看到如下輸出:

+ INFO ‘this is a info log‘ + ‘[‘ 2 -ge 2 ‘]‘ + echo -e ‘\033[32m INFO:\033[0m this is a info log‘ INFO: this is a info log + ERROR ‘this is a error log‘ + ‘[‘ 2 -ge 1 ‘]‘ + echo -e ‘\033[33m ERR:\033[0m this is a error log‘ ERR: this is a error log + set +x

如果想全程打開 xtrace,可以在執行腳本的時候加 -x 參數。

3 trap/bashdb (史詩技)

為了方便調試,BASH 也提供了陷阱機制。這跟之前介紹的兩種方法高級不少。我們可以利用 trap 這個內置命令來指定各個 sigspec 應該執行的命令。trap 的具體用法如下:

trap [-lp] [[arg] sigspec ...]

sigspec 包括 <signal.h> 中定義的各個 signal, EXIT,ERR,RETURN 和 DEBUG。

各個 signal 這裏就不介紹了。EXIT 會在 shell 退出時執行指定的命令。若當前 shell 中有命令執行返回非零值,則會執行與 ERR 相關聯的命令。而 RETURN 是針對 source.,每次執行都會觸發 RETURN 陷阱。若綁定一個命令到 DEBUG,則會在每一個命令執行之前,都會先執行 DEBUG 這個 trap。這裏要註意的是,ERR 和 DEBUG 只在當前 shell 有效。若想函數和子 shell 自動繼承這些 trap,則可以設置 -T(DEBUG/RETURN) 和 -E(ERR)。

比如,下面的腳本會在退出時,執行echo:

#!/bin/bash trap "echo this is a exit echo" EXIT echo "this is a normal echo"

或者,讓腳本中命令出錯時,把相應的命令打印出來:

#!/bin/bash trap ‘echo $BASH_COMMAND return err‘ ERR echo this is a normal test UnknownCmd

這個腳本的輸出如下:

this is a normal test tt.sh: line 6: UnknownCmd: command not found UnknownCmd return err

亦或者,讓腳本的命令單步執行:

#!/bin/bash trap ‘(read -p "[$0 : $LINENO] $BASH_COMMAND ?")‘ DEBUG echo this is a test i=0 while [ true ] do echo $i ((i++)) done

其輸出如下:

[tt.sh : 5] echo this is a test ? this is a test [tt.sh : 7] i=0 ? [tt.sh : 8] [ true ] ? [tt.sh : 10] echo $i ? 0 [tt.sh : 11] ((i++)) ? [tt.sh : 8] [ true ] ? [tt.sh : 10] echo $i ? 1 [tt.sh : 11] ((i++)) ? [tt.sh : 8] [ true ] ? [tt.sh : 10] echo $i ? 2 [tt.sh : 11] ((i++)) ?

是不是有點意思了?其實有一個 bashdb 的開源項目,也是利用 trap 機制,模擬 gdb 做了一個 bash 腳本的調試器。它本身也是一個 bash 腳本。在加載要調試的腳本後,可以用和 gdb 類似的命令,甚至縮寫也是一樣的,大家可以嘗試一下:)

(上個月沈迷於 Diablo3,最後發現自己臉不行,悴!還是回來寫點東西吧!)

Read More:

  • 如何制作終端中的動畫
  • BASH 中的空格
  • 在 BASH 中進行高效的目錄切換
  • Linux 命令 tr 介紹
  • 記錄命令行輸出日誌
    來源: <http://www.tinylab.org/bash-debugging-tools/>
Shell腳本測試總結

1. 腳本測試的苦難
因為腳本使用的自由度很大,對於程序員限制很少,功能實現的隨意性給測試帶來了不少困難。首先,很多Shell腳本編寫不規範,沒有同意的Shell腳本編程規範,其次,腳本參數配置與程序邏輯混雜,區分不清晰。往往腳本作者同時承擔多個開發任務,由於開發周期以及復雜的線上環境等原因,與其他腳本接口的溝通難以面面俱到,導致RD單元測試進行得很不充分。

2. 我們應該如何入手
首先,代碼走查結合動態單步跟蹤以及觀察日誌與文件輸出,網絡、CPU狀態。
然後,撰寫測試樁與驅動,白盒測試保證代碼邏輯中循環和分支都能夠走到,黑盒測試保證函數和功能腳本接口正確,輸入輸出符合設計預期。
對於異常處理,特別是變量的檢查需要特別關註,變量在使用前都需要進行檢查,是否為空?或者為0?對於文件名和路徑必須檢查,確認文件是否存在,路徑是否可達之後再進行後續操作。
另外,需要考慮所依賴的其他功能腳本以及二進制工具,這些功能性單元應該如何使用,調用後的返回會有哪些情況,對於正常和異常結果,腳本是否能夠捕捉到並且作出正確的判斷。

3. 靜態測試 && 動態測試
1) 新舊版本代碼對比
可以基於icafe平臺的codereview功能查看新舊版本的diff代碼行,對比升級點,及時與RD溝通確認,避免遺漏,保證測試的全面性。代碼對比的方式可能局限性比較大,適用於兩個連續版本間代碼結構無大的改動的情況,很多情況下,新版本的腳本會與之前的版本完全不同,Shell腳本與C語言模塊有一個很大的區別就是,IM 模塊C 代碼的前後版本實現的承接關系很明顯,但是Shell腳本不一定,可能後來的RD會將之前版本的腳本完全推翻。代碼結構完全不同,因此在這種情況下,我們應該直接進入代碼走查環節。

2) 代碼走查
全面、深入、細致地關註腳本分支、循環邏輯正確性。
例如:retrbs重啟腳本,在重啟PS平臺所有retrbs之後,需要清理PS平臺retras cache,新增的啟動方式升級分成兩種啟動方式,normal與continue模式,實際在codereview時發現normal方式重啟完成後清理cache,continue方式重啟完成後直接退出,這肯定是有問題的,因為按正常邏輯來說,不管那種啟動方式,在重啟完成之後都需要清理cache。


3) 搭建環境
搭建環境需要了解腳本的運行場景,運行頻率,環境依賴以及與其配合的上下文腳本及程序:
腳本執行時所處的目錄和配置文件
對應的產品模塊功能
數據的周期性更新
server間的ssh認證
網絡通信端口檢查
腳本中的使用的工具
腳本硬件要求
比如說:腳本在什麽目錄下執行,每天幾點鐘執行,執行的時候需要什麽數據以及工具提前準備好,等等。


4. 如何調試Shell腳本
1) 檢查語法錯誤:
一般來說我們可以通過修改shell腳本的源代碼,令其輸出相關的調試信息來定位錯誤,那有沒有不修改源代碼來調試shell腳本的方法呢?答案就是使用shell的執行選,下面是一些常用選項的用法:
-n 只讀取shell腳本,但不實際執行
-x 進入跟蹤方式,顯示所執行的每一條命令
-c "string" 從strings中讀取命令

“-n”可用於測試shell腳本是否存在語法錯誤,但不會實際執行命令。在shell腳本編寫完成之後,實際執行之前,首先使用“-n”選項來測試腳本是否存在語法錯誤是一個很好的習慣。因為某些shell腳本在執行時會對系統環境產生影響,比如生成或移動文件等,如果在實際執行才發現語法錯誤,您不得不手工做一些系統環境的恢復工作才能繼續測試這個腳本。

“-c”選項使shell解釋器從一個字符串中而不是從一個文件中讀取並執行shell命令。當需要臨時測試一小段腳本的執行結果時,可以使用這個選項,如下所示:
sh -c ‘a=1;b=2;let c=$a+$b;echo "c=$c"‘

"-x"選項可用來跟蹤腳本的執行,是調試shell腳本的強有力工具。“-x”選項使shell在執行腳本的過程中把它實際執行的每一個命令行顯示出來,並且在行首顯示一個"+"號。 "+"號後面顯示的是經過了變量替換之後的命令行的內容,有助於分析實際執行的是什麽命令。 “-x”選項使用起來簡單方便,可以輕松對付大多數的shell調試任務,應把其當作首選的調試手段。

2) 調試工具-bashdb
使用shell調試器bashdb,這是一個類似於GDB的調試工具,可以完成對shell腳本的斷點設置,單步執行,變量觀察等許多功能。

使用bashdb進行debug的常用命令
1.列出代碼和查詢代碼類:
l 列出當前行以下的10行
- 列出正在執行的代碼行的前面10行
. 回到正在執行的代碼行
w 列出正在執行的代碼行前後的代碼
/pat/ 向後搜索pat
?pat?向前搜索pat

2.Debug控制類:
h 幫助
help 命令 得到命令的具體信息
q 退出bashdb
x 算數表達式 計算算數表達式的值,並顯示出來
!!空格Shell命令 參數 執行shell命令
使用bashdb進行debug的常用命令(cont.)
控制腳本執行類:
n 執行下一條語句,遇到函數,不進入函數裏面執行,將函數當作黑盒
s n 單步執行n次,遇到函數進入函數裏面
b 行號n 在行號n處設置斷點
del 行號n 撤銷行號n處的斷點
c 行號n 一直執行到行號n處
R 重新啟動
Finish 執行到程序最後
cond n expr 條件斷點


5. 腳本測試的基本流程
1.靜態代碼檢查
2.單元測試1:針對每個功能函數撰寫驅動和樁,驗證所有分支
• 確認每個配置項以及設計的文件目錄是否在使用前進行檢查
• 確認所有的變量沒有向外傳播的危險
• 確認所產出的臨時文件沒有泄露,腳本自己會負責處理掉臨時文件
3.單元測試2:對於單個功能腳本sh -x XXX.sh 跟蹤腳本執行情況
4.集成測試1:對於所有腳本使用sh -x XXX.sh 跟蹤腳本執行情況
5.集成測試2:模擬腳本生產環境,周期性連續多次執行全部功能腳本,監控腳本性能以及日誌、臨時文件等狀態。

6. 腳本測試中遇到的問題和解決方案
1) 判斷一個數組是否為空:
【腳本內容】:
if [ -z ${pg_readyDatalist[@]} ]
then
…………
fi
【問題】:不可如此判斷,超過一個元素時,語法錯誤
【sh -x 執行】:
+ ‘[‘ -z model gtrindex ‘]‘
retrbs_restart.sh: line 366: [: model: binary operator expected
【原因】:
-z 只能判斷一個變量是否為空
判斷一個list是否為空,應該:
【解決】判斷list元素個數是否為0
例如: if [ ${#ps_retrbs[@]} -eq 0 ]

2) If語句判斷
【腳本內容】:
if [ -f ./$i]
then
echo "test"
fi
【問題】: .$i] 的“]”前面沒有空格,造成語法錯誤
【sh -x 執行】:./test.sh: line 3: [: missing `]
【原因】: If語句的條件判斷“[ ]”,“[”之後和“]”之前必須有空格
【解決】加上空格


3) 字符串判斷
【腳本內容】:
if [ "$1" = "continue" ] then
echo “succ”
fi
【問題】:$1為空,打印“succ”
【sh -x 執行】:succ
【原因】: $1為空會造成語法錯誤,返回0,繼續執行if代碼塊中的邏輯,導致判斷錯誤
【解決】修改成 if [ "a$1" = "acontinue" ]

4) 變量傳播
【腳本內容】:
func(){
for((i=0;i<$RETRY_TIMES;i++))
do
NOTICE "delBlacklist”
done
}
for (( i=0; i<pggroup_size; i++))
do
func()
done
【問題】:“i”的值自增之後會傳遞到外層調用腳本,導致外層調用腳本的循環跳過或死循環
【解決】避免使用i,j,k等常見的循環控制變量,使用自定義的變量名,如retry_count等
在shell函數中定義的變量加上local關鍵字


5) 命令連接
問題一:
【腳本內容】:
cd to_del; rm -rf *
【問題】:如果cd 目錄失敗,rm -rf * 會錯誤地刪除當前目錄下的所有文件
【解決】使用 && 連接 cd失敗將不會繼續執行後面的命令

問題二:
【腳本內容】:
for data in ${datalist{@}}
do
runRemoteCmd ${host} "cd ${data_path}.new && [[ -f ${data_flag} ]]" || suc=0 && break
done
【問題】:這裏的 || && 是同一個優先級
那麽就是說 && 後面的語句 break無論什麽情況下都不可能被執行到
【解決】拆成兩條語句,單獨判斷suc


6) 文件泄露
【腳本內容】:
local status=$( mySsh ${remote_host} "{ ${command%%;}; }&>/tmp/$$ && echo 0 || echo 1" )
【問題】:上述代碼將遠程執行命令行的輸出結果導入到一個以pid命名的臨時文件中,在腳本關閉的時候沒有清除,每一次執行將創建一個新文件,很可能導致文件泄露問題。
【解決】註意清理腳本生成的臨時文件

7) ssh 遠程執行後臺命令不靠譜
【腳本內容】:
ssh hostname "cat bin &“
【執行】
[[email protected] bin]$ ssh localhost "cat bin &"
cat: bin: Is a directory
[[email protected] bin]$ echo $?
0
【問題】:命令執行錯誤,返回值為0
【解決】將遠程命令放在前臺執行:
[[email protected] bin]$ ssh localhost "cat bin"
cat: bin: Is a directory
[[email protected] bin]$ echo $?
1

8) 變量使用前使用unset清理
【腳本內容】:一般是針對腳本的配置文件
ps_retras[0]="[email protected]"
ps_retras[1]="[email protected]"
【問題】:如果OP修改ps_retras數組的配置,可能無法生效
【解決】使用unset進行清理
unset
功能說明:刪除變量或函數。
語法:unset [-fv][變量或函數名稱]
參數:
-f 僅刪除函數。
-v 僅刪除變量。
例如:unset ps_retras
ps_retras[0]="[email protected]"
ps_retras[1]=“[email protected]“

7. shell 內置變量
1) $FUNCNAME
函數的名字,類似於C語言中的內置宏__func__,但宏__func__ 只能代表當前所在的函數名,而$FUNCNAME的功能更強大,它是一個數組變量,其中包含了整個調用鏈上所有的函數的名字,故變量${FUNCNAME [0]}代表shell腳本當前正在執行的函數的名字,而變量${FUNCNAME[1]}則代表調用函數${FUNCNAME[0]}的函數的名字,依此類推。

2) $BASH_SOURCE
shell腳本源文件名,與FUNCNAME相對應

3) $BASH_LINENO
代表shell腳本的當前行號,類似於C語言中的內置宏__LINE__,與FUNCNAME相關聯
BASH_LINENO[$i] 指示的是 FUNCNAME[$i + 1]被調用的位置

4) $PS4
第四級提示符變量$PS4 , $PS4的值將被顯示在“-x”選項輸出的每一條命令的前面。在Bash Shell中,缺省的$PS4的值是"+"號。(現在知道為什麽使用"-x"選項時,輸出的命令前面有一個"+"號了吧 )
通過修改$PS4的值,就可以達到sh –x 時顯示行號還有函數名稱的目的了。

================= End

bash腳本測試總結