使用trap/tee命令/調試鉤子調試Shell腳本

 本文全面系統地介紹了shell腳本調試技術,包括使用echo, tee, trap等命令輸出關鍵信息,跟蹤變量的值,在腳本中植入調試鉤子,使用“-n”選項進行shell腳本的語法檢查,使用“-x”選項實現shell腳本逐條語句的跟蹤,巧妙地利用shell的內置變量增強“-x”選項的輸出信息等。
一. 前言
     我們之所以要進行Shell腳本調試,就是爲了發現引發腳本錯誤的原因以及在腳本源代碼中定位發生錯誤的行,常用的手段包括分析輸出的錯誤信息,在腳本中加入調試語句,輸出調試信息來輔助診斷錯誤,利用調試工具等。但與其它高級語言(java,c++等)相比,shell解釋器缺乏相應的調試機制和調試工具的支持,其輸出的錯誤信息又往往很不明確,初學者在調試腳本時,除了知道用echo語句輸出一些信息外,別無它法,而僅僅依賴於大量的加入echo語句來診斷錯誤,確實令人不勝其繁,故常見初學者抱怨shell腳本太難調試了。
    現在我們系統地學習一些重要的shell腳本調試技術,相信對很多shell的初學者有很大幫助的。
    閱讀下面內容前,要求讀者具有基本的shell編程知識。本文實驗平臺是Bash3.1+Redhat Enterprise Server 4.0,但所介紹的調試技巧應該也同樣適用於其它shell和Unix平臺。
二. 在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命令了。
    tee命令會從標準輸入讀取數據,將其內容輸出到標準輸出設備,同時又可將內容保存成文件。例如有如下的腳本片段,其作用是獲取本機的ip地址:
ipaddr=`/sbin/ifconfig | grep 'inet addr:' | grep -v '127.0.0.1'
| cut -d : -f3 | awk '{print $1}'`
#注意=號後面的整句是用反引號(數字1鍵的左邊那個鍵)括起來的。
echo $ipaddr
   運行這個腳本,實際輸出的卻不是本機的ip地址,而是廣播地址,這時我們可以藉助tee命令,輸出某些中間結果,將上述腳本片段修改爲:
ipaddr=`/sbin/ifconfig | grep 'inet addr:' | grep -v '127.0.0.1'
| tee temp.txt | cut -d : -f3 | awk '{print $1}'`
echo $ipaddr
  之後,將這段腳本再執行一遍,然後查看temp.txt文件的內容:
$ cat temp.txt
inet addr:192.168.0.1  Bcast:192.168.0.255  Mask:255.255.255.0
    我們可以發現中間結果的第二列(列之間以:號分隔)才包含了IP地址,而在上面的腳本中使用cut命令截取了第三列,所以我們只需將腳本中的cut -d : -f3改爲cut -d : -f2即可得到正確的結果。
    具體到上述的腳本例子,我們也許並不需要tee命令的幫助,比如我們可以分段執行由管道連接起來的各條命令並查看各命令的輸出結果來診斷錯誤,但在一些複雜的shell腳本中,這些由管道連接起來的命令可能又依賴於腳本中定義的一些其它變量,這時我們想要在提示符下來分段運行各條命令就會非常麻煩了,簡單地在管道之間插入一條tee命令來查看中間結果會更方便一些。
3. 使用"調試鉤子"
    學過C語言程序的朋友有這個印象嗎:我們經常使用DEBUG宏來控制是否要輸出調試信息,在shell腳本中我們同樣可以使用這樣的機制,如下列代碼所示:
if [ “$DEBUG” = “true” ]; then
echo “debugging”  #此處可以輸出調試信息
fi
    這樣的代碼塊通常稱之爲“調試鉤子”或“調試塊”。在調試鉤子內部可以輸出任何您想輸出的調試信息,使用調試鉤子的好處是它是可以通過DEBUG變量來控制的,在腳本的開發調試階段,可以先執行export DEBUG=true命令打開調試鉤子,使其輸出調試信息,而在把腳本交付使用時,也無需再費事把腳本中的調試語句一一刪除。
    如果在每一處需要輸出調試信息的地方均使用if語句來判斷DEBUG變量的值,還是顯得比較繁瑣,通過定義一個DEBUG函數可以使植入調試鉤子的過程更簡潔方便,如下面代碼所示:
$ cat –n exp3.sh
     1  DEBUG()
     2  {
     3  if [ "$DEBUG" = "true" ]; then
     4      $@  
     5  fi
     6  }
     7  a=1
     8  DEBUG echo "a=$a"
     9  if [ "$a" -eq 1 ]
    10  then
    11       b=2
    12  else
    13       b=1
    14  fi
    15  DEBUG echo "b=$b"
    16  c=3
    17  DEBUG echo "c=$c"
    在上面所示的DEBUG函數中,會執行任何傳給它的命令,並且這個執行過程是可以通過DEBUG變量的值來控制的,我們可以把所有跟調試有關的命令都作爲DEBUG函數的參數來調用,非常的方便。


本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u2/77896/showart_1160697.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章