腳本除錯(cuò)
本章介紹如何對 Shell 腳本除錯(cuò)麻汰。
常見錯(cuò)誤
編寫 Shell 腳本的時(shí)候速客,一定要考慮到命令失敗的情況,否則很容易出錯(cuò)五鲫。
#! /bin/bash
dir_name=/path/not/exist
cd $dir_name
rm *
上面腳本中溺职,如果目錄$dir_name
不存在,cd $dir_name
命令就會執(zhí)行失敗。這時(shí)浪耘,就不會改變當(dāng)前目錄智亮,腳本會繼續(xù)執(zhí)行下去,導(dǎo)致rm *
命令刪光當(dāng)前目錄的文件点待。
如果改成下面的樣子阔蛉,也會有問題。
cd $dir_name && rm *
上面腳本中癞埠,只有cd $dir_name
執(zhí)行成功状原,才會執(zhí)行rm *
。但是苗踪,如果變量$dir_name
為空颠区,cd
就會進(jìn)入用戶主目錄,從而刪光用戶主目錄的文件通铲。
下面的寫法才是正確的毕莱。
[[ -d $dir_name ]] && cd $dir_name && rm *
上面代碼中,先判斷目錄$dir_name
是否存在颅夺,然后才執(zhí)行其他操作朋截。
如果不放心刪除什么文件,可以先打印出來看一下吧黄。
[[ -d $dir_name ]] && cd $dir_name && echo rm *
上面命令中部服,echo rm *
不會刪除文件,只會打印出來要?jiǎng)h除的文件拗慨。
bash
的-x
參數(shù)
bash
的-x
參數(shù)可以在執(zhí)行每一行命令之前廓八,打印該命令。一旦出錯(cuò)赵抢,這樣就比較容易追查剧蹂。
下面是一個(gè)腳本script.sh
。
# script.sh
echo hello world
加上-x
參數(shù)烦却,執(zhí)行每條命令之前宠叼,都會顯示該命令。
$ bash -x script.sh
+ echo hello world
hello world
上面例子中短绸,行首為+
的行车吹,顯示該行是所要執(zhí)行的命令,下一行才是該命令的執(zhí)行結(jié)果醋闭。
下面再看一個(gè)-x
寫在腳本內(nèi)部的例子窄驹。
#! /bin/bash -x
# trouble: script to demonstrate common errors
number=1
if [ $number = 1 ]; then
echo "Number is equal to 1."
else
echo "Number is not equal to 1."
fi
上面的腳本執(zhí)行之后,會輸出每一行命令证逻。
$ trouble
+ number=1
+ '[' 1 = 1 ']'
+ echo 'Number is equal to 1.'
Number is equal to 1.
輸出的命令之前的+
號乐埠,是由系統(tǒng)變量PS4
決定,可以修改這個(gè)變量。
$ export PS4='$LINENO + '
$ trouble
5 + number=1
7 + '[' 1 = 1 ']'
8 + echo 'Number is equal to 1.'
Number is equal to 1.
另外丈咐,set
命令也可以設(shè)置 Shell 的行為參數(shù)瑞眼,有利于腳本除錯(cuò),詳見《set 命令》一章棵逊。
環(huán)境變量
有一些環(huán)境變量常用于除錯(cuò)伤疙。
LINENO
變量LINENO
返回它在腳本里面的行號。
#!/bin/bash
echo "This is line $LINENO"
執(zhí)行上面的腳本test.sh
辆影,$LINENO
會返回3
徒像。
$ ./test.sh
This is line 3
FUNCNAME
變量FUNCNAME
返回一個(gè)數(shù)組,內(nèi)容是當(dāng)前的函數(shù)調(diào)用堆棧蛙讥。該數(shù)組的0號成員是當(dāng)前調(diào)用的函數(shù)锯蛀,1號成員是調(diào)用當(dāng)前函數(shù)的函數(shù),以此類推次慢。
#!/bin/bash
function func1()
{
echo "func1: FUNCNAME0 is ${FUNCNAME[0]}"
echo "func1: FUNCNAME1 is ${FUNCNAME[1]}"
echo "func1: FUNCNAME2 is ${FUNCNAME[2]}"
func2
}
function func2()
{
echo "func2: FUNCNAME0 is ${FUNCNAME[0]}"
echo "func2: FUNCNAME1 is ${FUNCNAME[1]}"
echo "func2: FUNCNAME2 is ${FUNCNAME[2]}"
}
func1
執(zhí)行上面的腳本test.sh
旁涤,結(jié)果如下。
$ ./test.sh
func1: FUNCNAME0 is func1
func1: FUNCNAME1 is main
func1: FUNCNAME2 is
func2: FUNCNAME0 is func2
func2: FUNCNAME1 is func1
func2: FUNCNAME2 is main
上面例子中迫像,執(zhí)行func1
時(shí)劈愚,變量FUNCNAME
的0號成員是func1
,1號成員是調(diào)用func1
的主腳本main
侵蒙。執(zhí)行func2
時(shí)造虎,變量FUNCNAME
的0號成員是func2
,1號成員是調(diào)用func2
的func1
纷闺。
BASH_SOURCE
變量BASH_SOURCE
返回一個(gè)數(shù)組,內(nèi)容是當(dāng)前的腳本調(diào)用堆棧份蝴。該數(shù)組的0號成員是當(dāng)前執(zhí)行的腳本犁功,1號成員是調(diào)用當(dāng)前腳本的腳本,以此類推婚夫,跟變量FUNCNAME
是一一對應(yīng)關(guān)系浸卦。
下面有兩個(gè)子腳本lib1.sh
和lib2.sh
。
# lib1.sh
function func1()
{
echo "func1: BASH_SOURCE0 is ${BASH_SOURCE[0]}"
echo "func1: BASH_SOURCE1 is ${BASH_SOURCE[1]}"
echo "func1: BASH_SOURCE2 is ${BASH_SOURCE[2]}"
func2
}
# lib2.sh
function func2()
{
echo "func2: BASH_SOURCE0 is ${BASH_SOURCE[0]}"
echo "func2: BASH_SOURCE1 is ${BASH_SOURCE[1]}"
echo "func2: BASH_SOURCE2 is ${BASH_SOURCE[2]}"
}
然后案糙,主腳本main.sh
調(diào)用上面兩個(gè)子腳本限嫌。
#!/bin/bash
# main.sh
source lib1.sh
source lib2.sh
func1
執(zhí)行主腳本main.sh
,會得到下面的結(jié)果时捌。
$ ./main.sh
func1: BASH_SOURCE0 is lib1.sh
func1: BASH_SOURCE1 is ./main.sh
func1: BASH_SOURCE2 is
func2: BASH_SOURCE0 is lib2.sh
func2: BASH_SOURCE1 is lib1.sh
func2: BASH_SOURCE2 is ./main.sh
上面例子中怒医,執(zhí)行函數(shù)func1
時(shí),變量BASH_SOURCE
的0號成員是func1
所在的腳本lib1.sh
奢讨,1號成員是主腳本main.sh
稚叹;執(zhí)行函數(shù)func2
時(shí),變量BASH_SOURCE
的0號成員是func2
所在的腳本lib2.sh
,1號成員是調(diào)用func2
的腳本lib1.sh
扒袖。
BASH_LINENO
變量BASH_LINENO
返回一個(gè)數(shù)組塞茅,內(nèi)容是每一輪調(diào)用對應(yīng)的行號。${BASH_LINENO[$i]}
跟${FUNCNAME[$i]}
是一一對應(yīng)關(guān)系季率,表示${FUNCNAME[$i]}
在調(diào)用它的腳本文件${BASH_SOURCE[$i+1]}
里面的行號野瘦。
下面有兩個(gè)子腳本lib1.sh
和lib2.sh
。
# lib1.sh
function func1()
{
echo "func1: BASH_LINENO is ${BASH_LINENO[0]}"
echo "func1: FUNCNAME is ${FUNCNAME[0]}"
echo "func1: BASH_SOURCE is ${BASH_SOURCE[1]}"
func2
}
# lib2.sh
function func2()
{
echo "func2: BASH_LINENO is ${BASH_LINENO[0]}"
echo "func2: FUNCNAME is ${FUNCNAME[0]}"
echo "func2: BASH_SOURCE is ${BASH_SOURCE[1]}"
}
然后飒泻,主腳本main.sh
調(diào)用上面兩個(gè)子腳本缅刽。
#!/bin/bash
# main.sh
source lib1.sh
source lib2.sh
func1
執(zhí)行主腳本main.sh
,會得到下面的結(jié)果蠢络。
$ ./main.sh
func1: BASH_LINENO is 7
func1: FUNCNAME is func1
func1: BASH_SOURCE is main.sh
func2: BASH_LINENO is 8
func2: FUNCNAME is func2
func2: BASH_SOURCE is lib1.sh
上面例子中衰猛,函數(shù)func1
是在main.sh
的第7行調(diào)用,函數(shù)func2
是在lib1.sh
的第8行調(diào)用的刹孔。