j腳本除錯
如何考慮命令出錯的情況锣吼。
比如:
#! /bin/bash
dir_name=/path/not/exist
cd $dir_name
rm *
上面腳本中,如果目錄$dir_name
不存在古徒,cd $dir_name
命令就會執(zhí)行失敗读恃。如果$dir_name
為空,則會進入主目錄刪除所有文件疹吃。
以下寫法才是正確:
[[ -d $dir_name ]] && cd $dir_name && rm *
如果不放心刪除什么文件西雀,可以先打印出來看一下:
[[ -d $dir_name ]] && cd $dir_name && echo rm *
上面命令中艇肴,echo rm *
不會刪除文件,只會打印出來要刪除的文件核畴。
bash的-x
參數(shù)可以在執(zhí)行每一行命令之前冲九,打印該命令:
比如:
# script.sh
echo hello world
加上參數(shù)-x
之后的執(zhí)行結(jié)果如下:
$ bash -x script.sh
+ echo hello world
hello world
也可以將-x
寫在Shebang行中:
#! /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í)行結(jié)果如下:
$ trouble
+ number=1
+ '[' 1 = 1 ']'
+ echo 'Number is equal to 1.'
Number is equal to 1.
輸出的命令之前的+號,是由系統(tǒng)變量PS4決定,可以修改這個變量:
$ export PS4='$LINENO + '
$ trouble
5 + number=1
7 + '[' 1 = 1 ']'
8 + echo 'Number is equal to 1.'
Number is equal to 1.
環(huán)境變量:
變量$LINENO
返回它在腳本里面的行號:
#!/bin/bash
echo "This is line $LINENO"
運行結(jié)果如下:
$ ./test.sh
This is line 3
變量$FUNCNAME
返回一個數(shù)組憾筏,內(nèi)容是當(dāng)前的函數(shù)調(diào)用堆棧花鹅。該數(shù)組的0號成員是當(dāng)前調(diào)用的函數(shù),1號成員是調(diào)用當(dāng)前函數(shù)的函數(shù)古拴,以此類推:
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
運行結(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
可以發(fā)現(xiàn)黄痪,有一個main函數(shù)盔然。
變量$BASH_SOURCE
返回一個數(shù)組是嗜,內(nèi)容是當(dāng)前的腳本調(diào)用堆棧挺尾。該數(shù)組的0號成員是當(dāng)前執(zhí)行的腳本,1號成員是調(diào)用當(dāng)前腳本的腳本丽柿,以此類推魂挂,跟變量$FUNCNAME
是一一對應(yīng)關(guān)系锰蓬。
下面有兩個子腳本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)用上面兩個子腳本:
#!/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
變量$BASH_LINENO
返回一個數(shù)組舱卡,內(nèi)容是每一輪調(diào)用對應(yīng)的行號。${BASH_LINENO[$i]}
跟${FUNCNAME[$i]}
是一一對應(yīng)關(guān)系矫钓,表示${FUNCNAME[$i]}
在調(diào)用它的腳本文件${BASH_SOURCE[$i+1]}
里面的行號舍杜。
下面有兩個子腳本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)用上面兩個子腳本:
#!/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)用的。
mktemp 命令铝耻,trap 命令
Bash 腳本有時需要創(chuàng)建臨時文件或臨時目錄蹬刷。常見的做法是替废,在/tmp
目錄里面創(chuàng)建文件或目錄椎镣,這樣做有很多弊端兽赁,使用mktemp
命令是最安全的做法。
/tmp目錄是所有人可讀寫的惊科,任何用戶都可以往該目錄里面寫文件亮钦。創(chuàng)建的臨時文件也是所有人可讀的:
$ touch /tmp/info.txt
$ ls -l /tmp/info.txt
-rw-r--r-- 1 ruanyf ruanyf 0 12月 28 17:12 /tmp/info.txt
因此,臨時文件最好使用不可預(yù)測蜡娶、每次都不一樣的文件名映穗,防止被利用。臨時文件使用完畢宿接,應(yīng)該刪除辕录。但是,腳本意外退出時碎赢,往往會忽略清理臨時文件速梗。
生成臨時文件應(yīng)該遵循下面的規(guī)則:
創(chuàng)建前檢查文件是否已經(jīng)存在襟齿。
確保臨時文件已成功創(chuàng)建猜欺。
臨時文件必須有權(quán)限的限制。
臨時文件要使用不可預(yù)測的文件名涧黄。
腳本退出時,要刪除臨時文件(使用trap命令)笋妥。
mktemp
命令就是為安全創(chuàng)建臨時文件而設(shè)計的春宣。雖然在創(chuàng)建臨時文件之前,它不會檢查臨時文件是否存在躏惋,但是它支持唯一文件名和清除機制嚷辅,因此可以減輕安全攻擊的風(fēng)險。
直接運行mktemp命令扁位,就能生成一個臨時文件:
$ mktemp
/tmp/tmp.4GcsWSG4vj
$ ls -l /tmp/tmp.4GcsWSG4vj
-rw------- 1 ruanyf ruanyf 0 12月 28 12:49 /tmp/tmp.4GcsWSG4vj
Bash 腳本使用mktemp命令的用法如下:
#!/bin/bash
TMPFILE=$(mktemp)
echo "Our temp file is $TMPFILE"
為了確保臨時文件創(chuàng)建成功攘乒,mktemp命令后面最好使用 OR 運算符||
则酝,保證創(chuàng)建失敗時退出腳本:
#!/bin/bash
TMPFILE=$(mktemp) || exit 1
echo "Our temp file is $TMPFILE"
為了保證腳本退出時臨時文件被刪除,可以使用trap命令指定退出時的清除操作:
#!/bin/bash
trap 'rm -f "$TMPFILE"' EXIT
TMPFILE=$(mktemp) || exit 1
echo "Our temp file is $TMPFILE"
mktemp 命令的參數(shù):
-d
:可以創(chuàng)建一個臨時目錄:
$ mktemp -d
/tmp/tmp.Wcau5UjmN6
-p
:可以指定臨時文件所在的目錄般卑。默認是使用$TMPDIR
環(huán)境變量指定的目錄爽雄,如果這個變量沒設(shè)置,那么使用/tmp
目錄:
$ mktemp -p /home/ruanyf/
/home/ruanyf/tmp.FOKEtvs2H3
-t
:可以指定臨時文件的文件名模板叹谁,模板的末尾必須至少包含三個連續(xù)的X字符乘盖,表示隨機字符,建議至少使用六個X析苫。默認的文件名模板是tmp.后接十個隨機字符:
$ mktemp -t mytemp.XXXXXXX
/tmp/mytemp.yZ1HgZV
trap命令:
trap命令用來在 Bash 腳本中響應(yīng)系統(tǒng)信號衩侥。
最常見的系統(tǒng)信號就是 SIGINT(中斷),即按 Ctrl + C 所產(chǎn)生的信號茫死。trap命令的-l
參數(shù)璧榄,可以列出所有的系統(tǒng)信號:
$ trap -l
trap的命令格式如下:
$ trap [動作] [信號1] [信號2] ...
上面代碼中,“動作”是一個 Bash 命令涂身,“信號”常用的有以下幾個:
HUP:編號1搓蚪,腳本與所在的終端脫離聯(lián)系。
INT:編號2悴能,用戶按下 Ctrl + C雳灾,意圖讓腳本終止運行。
QUIT:編號3炒嘲,用戶按下 Ctrl + 斜杠匈庭,意圖退出腳本阱持。
KILL:編號9,該信號用于殺死進程衷咽。
TERM:編號15镶骗,這是kill命令發(fā)出的默認信號。
EXIT:編號0,這不是系統(tǒng)信號,而是 Bash 腳本特有的信號即横,不管什么情況裆赵,只要退出腳本就會產(chǎn)生战授。
trap命令響應(yīng)EXIT信號的寫法如下:
$ trap 'rm -f "$TMPFILE"' EXIT
上面命令中,腳本遇到EXIT信號時份帐,就會執(zhí)行rm -f "$TMPFILE"
楣导。
trap 命令的常見使用場景,就是在 Bash 腳本中指定退出時執(zhí)行的清理命令:
#!/bin/bash
trap 'rm -f "$TMPFILE"' EXIT
TMPFILE=$(mktemp) || exit 1
ls /etc > $TMPFILE
if grep -qi "kernel" $TMPFILE; then
echo 'find'
fi
上面代碼中噩凹,不管是腳本正常執(zhí)行結(jié)束毡咏,還是用戶按 Ctrl + C 終止,都會產(chǎn)生EXIT信號堵泽,從而觸發(fā)刪除臨時文件臊旭。
注意离熏,trap命令必須放在腳本的開頭。否則钻蔑,它上方的任何命令導(dǎo)致腳本退出奸鸯,都不會被它捕獲。
如果trap需要觸發(fā)多條命令窗怒,可以封裝一個 Bash 函數(shù):
function egress {
command1
command2
command3
}
trap egress EXIT
登錄session&非登錄session
登錄session:
登錄 Session 是用戶登錄系統(tǒng)以后,系統(tǒng)為用戶開啟的原始 Session努隙,通常需要用戶輸入用戶名和密碼進行登錄辜昵。
登錄 Session 一般進行整個系統(tǒng)環(huán)境的初始化堪置,啟動的初始化腳本依次如下:
/etc/profile:所有用戶的全局配置腳本。
/etc/profile.d目錄里面所有.sh文件
~/.bash_profile:用戶的個人配置腳本岭洲。如果該腳本存在雁竞,則執(zhí)行完就不再往下執(zhí)行。
~/.bash_login:如果~/.bash_profile沒找到彪腔,則嘗試執(zhí)行這個腳本(C shell 的初始化腳本)进栽。如果該腳本存在,則執(zhí)行完就不再往下執(zhí)行格嗅。
~/.profile:如果~/.bash_profile和~/.bash_login都沒找到屯掖,則嘗試讀取這個腳本(Bourne shell 和 Korn shell 的初始化腳本)襟衰。
下面是一個典型的.bash_profile
文件:
# .bash_profile
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
PATH=$PATH:$HOME/bin
SHELL=/bin/bash
MANPATH=/usr/man:/usr/X11/man
EDITOR=/usr/bin/vi
PS1='\h:\w\$ '
PS2='> '
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
export PATH
export EDITOR
非登錄session:
非登錄 Session 是用戶進入系統(tǒng)以后瀑晒,手動新建的 Session,這時不會進行環(huán)境初始化苔悦。比如玖详,在命令行執(zhí)行bash命令勤讽,就會新建一個非登錄 Session拗踢。
非登錄 Session 的初始化腳本依次如下:
/etc/bash.bashrc:對全體用戶有效秒拔。
~/.bashrc:僅對當(dāng)前用戶有效飒硅。
注意三娩,執(zhí)行腳本相當(dāng)于新建一個非互動的 Bash 環(huán)境,但是這種情況不會調(diào)用~/.bashrc
雀监。
~/.bash_logout
腳本在每次退出 Session 時執(zhí)行会前,通常用來做一些清理工作和記錄工作,比如刪除臨時文件蔚万,記錄用戶在本次 Session 花費的時間临庇。
為了方便 Debug假夺,有時在啟動 Bash 的時候,可以加上啟動參數(shù):
-n:不運行腳本梧田,只檢查是否有語法錯誤悼尾。
-v:輸出每一行語句運行結(jié)果前,會先輸出該行語句未状。
-x:每一個命令處理之前司草,先輸出該命令,再執(zhí)行該命令猜憎。
$ bash -n scriptname
$ bash -v scriptname
$ bash -x scriptname
命令提示符
用戶進入 Bash 以后搔课,Bash 會顯示一個命令提示符,用來提示用戶在該位置后面輸入命令柬讨。
命令提示符通常是美元符號$袍啡,對于根用戶則是井號#境输。這個符號是環(huán)境變量PS1決定的,執(zhí)行下面的命令辩越,可以看到當(dāng)前命令提示符的定義:
$ echo $PS1
Bash 允許用戶自定義命令提示符区匣,只要改寫這個變量即可蒋院。改寫后的PS1,可以放在用戶的 Bash 配置文件.bashrc里面姑丑,以后新建 Bash 對話時辞友,新的提示符就會生效。要在當(dāng)前窗口看到修改后的提示符称龙,可以執(zhí)行下面的命令:
$ source ~/.bashrc
除了PS1鲫尊,Bash 還提供了提示符相關(guān)的另外三個環(huán)境變量。
環(huán)境變量PS2是命令行折行輸入時系統(tǒng)的提示符咳蔚,默認為>
:
$ echo "hello
> world"
環(huán)境變量PS3是使用select命令時谈火,系統(tǒng)輸入菜單的提示符:
環(huán)境變量PS4默認為+
。它是使用 Bash 的-x
參數(shù)執(zhí)行腳本時扔字,每一行命令在執(zhí)行前都會先打印出來温技,并且在行首出現(xiàn)的那個提示符荒揣。
比如下面是腳本test.sh:
#!/bin/bash
echo "hello world"
使用-x
參數(shù)執(zhí)行這個腳本:
$ bash -x test.sh
+ echo 'hello world'
hello world