本文全面系統地介紹了shell腳本調試技術,包括使用echo, tee, trap等命令輸出關鍵信息,跟蹤變量的值,在腳本中植入調試鉤子,使用“-n”選項進行shell腳本的語法檢查, 使用“-x”選項實現shell腳本逐條語句的跟蹤,巧妙地利用shell的內置變量增強“-x”選項的輸出信息等。
一. 前言
shell編程在unix/linux世界中使用得非常廣泛,熟練掌握shell編程也是成為一名優秀的unix/linux開發者和系統管理員的必經之路。腳本調試的主要工作就是發現引發腳本錯誤的原因以及在腳本源代碼中定位發生錯誤的行,常用的手段包括分析輸出的錯誤信息,通過在腳本中加入調試語句,輸出調試信息來輔助診斷錯誤,利用調試工具等。但與其它高級語言相比,shell解釋器缺乏相應的調試機制和調試工具的支持,其輸出的錯誤信息又往往很不明確,初學者在調試腳本時,除了知道用echo語句輸出一些信息外,別無它法,而僅僅依賴於大量的加入echo語句來診斷錯誤,確實令人不勝其繁,故常見初學者抱怨shell腳本太難調試了。本文將系統地介紹一些重要的shell腳本調試技術,希望能對shell的初學者有所裨益。
本文的目標讀者是unix/linux環境下的開發人員,測試人員和系統管理員,要求讀者具有基本的shell編程知識。本文所使用范例在Bash3.1+Redhat Enterprise Server 4.0下測試通過,但所述調試技巧應也同樣適用於其它shell。
二. 在shell腳本中輸出調試信息
通過在程序中加入調試語句把一些關鍵地方或出錯的地方的相關信息顯示出來是最常見的調試手段。Shell程序員通常使用echo(ksh程序員常使用print)語句輸出信息,但僅僅依賴echo語句的輸出跟蹤信息很麻煩,調試階段在腳本中加入的大量的echo語句在產品交付時還得再費力一一刪除。針對這個問題,本節主要介紹一些如何方便有效的輸出調試信息的方法。
1. 使用trap命令
trap命令用於捕獲指定的信號並執行預定義的命令。
其基本的語法是:
trap 'command' signal
其中signal是要捕獲的信號,command是捕獲到指定的信號之後,所要執行的命令。可以用kill –l命令看到系統中全部可用的信號名,捕獲信號後所執行的命令可以是任何一條或多條合法的shell語句,也可以是一個函數名。
shell腳本在執行時,會產生三個所謂的“偽信號”,(之所以稱之為“偽信號”是因為這三個信號是由shell產生的,而其它的信號是由操作系統產生的),通過使用trap命令捕獲這三個“偽信號”並輸出相關信息對調試非常有幫助。
表 1. shell偽信號
信號名 何時產生
EXIT 從一個函數中退出或整個腳本執行完畢
ERR 當一條命令返回非零狀態時(代表命令執行不成功)
DEBUG 腳本中每一條命令執行之前
通過捕獲EXIT信號,我們可以在shell腳本中止執行或從函數中退出時,輸出某些想要跟蹤的變量的值,並由此來判斷腳本的執行狀態以及出錯原因,其使用方法是:
trap 'command' EXIT 或 trap 'command' 0
通過捕獲ERR信號,我們可以方便的追蹤執行不成功的命令或函數,並輸出相關的調試信息,以下是一個捕獲ERR信號的示例程序,其中的$LINENO是一個shell的內置變量,代表shell腳本的當前行號。
$ cat -n exp1.sh 1 ERRTRAP() 2 { 3 echo "[LINE:$1] Error: Command or function exited with status $?" 4 } 5 foo() 6 { 7 return 1; 8 } 9 trap 'ERRTRAP $LINENO' ERR 10 abc 11 foo
其輸出結果如下:
$ sh exp1.sh exp1.sh: line 10: abc: command not found [LINE:10] Error: Command or function exited with status 127 [LINE:11] Error: Command or function exited with status 1
在調試過程中,為了跟蹤某些變量的值,我們常常需要在shell腳本的許多地方插入相同的echo語句來打印相關變量的值,這種做法顯得煩瑣而笨拙。而通過捕獲DEBUG信號,我們只需要一條trap語句就可以完成對相關變量的全程跟蹤。
以下是一個通過捕獲DEBUG信號來跟蹤變量的示例程序:
$ cat –n exp2.sh 1 #!/bin/bash 2 trap 'echo “before execute line:$LINENO, a=$a,b=$b,c=$c”' DEBUG 3 a=1 4 if [ "$a" -eq 1 ] 5 then 6 b=2 7 else 8 b=1 9 fi 10 c=3 11 echo "end"
其輸出結果如下:
$ sh exp2.sh before execute line:3, a=,b=,c= before execute line:4, a=1,b=,c= before execute line:6, a=1,b=,c= before execute line:10, a=1,b=2,c= before execute line:11, a=1,b=2,c=3 end
從運行結果中可以清晰的看到每執行一條命令之後,相關變量的值的變化。同時,從運行結果中打印出來的行號來分析,可以看到整個腳本的執行軌跡,能夠判斷出哪些條件分支執行了,哪些條件分支沒有執行。
2. 使用tee命令
在shell腳本中管道以及輸入輸出重定向使用得非常多,在管道的作用下,一些命令的執行結果直接成為了下一條命令的輸入。如果我們發現由管道連接起來的一批命令的執行結果並非如預期的那樣,就需要逐步檢查各條命令的執行結果來判斷問題出在哪兒,但因為使用了管道,這些中間結果並不會顯示在屏幕上,給調試帶來了困難,此時我們就可以借助於tee命令了。