簡(jiǎn)單shell腳本
!/bin/bash
這一行表明,不管用戶選擇的是那種交互式shell,該腳本需要使用bash shell來(lái)運(yùn)行。由于每種shell的語(yǔ)法大不相同,所以這句非常重要。
簡(jiǎn)單實(shí)例
下面是一個(gè)非常簡(jiǎn)單的shell腳本。它只是運(yùn)行了幾條簡(jiǎn)單的命令
#!/bin/bash
echo "hello, $USER. I wish to list some files of yours"
echo "listing files in the current directory, $PWD"
ls # 列出當(dāng)前目錄所有文件
首先,請(qǐng)注意第四行。在bash腳本中,跟在#符號(hào)之后的內(nèi)容都被認(rèn)為是注釋(除了第一行)。Shell會(huì)忽略注釋。這樣有助于用戶閱讀理解腳本。 ?$USER和 $PWD都是變量。它們是bash腳本自定義的標(biāo)準(zhǔn)變量,無(wú)需在腳本中定義即可使用。請(qǐng)注意,在雙引號(hào)中引用的變量會(huì)被展開(expanded)?!癳xpanded”是一個(gè)非常合適的形容詞:基本上,當(dāng)shell執(zhí)行命令并遇到$USER變量時(shí),會(huì)將其替換為該變量對(duì)應(yīng)的值。
變量
任何編程語(yǔ)言都會(huì)用到變量。你可以使用下面的語(yǔ)句來(lái)定義一個(gè)變量:
X="hello"
并按下面的格式來(lái)引用這個(gè)變量:
$X
更具體的說(shuō),$X表示變量X的值。關(guān)于語(yǔ)義方面有如下幾點(diǎn)需要注意:
等于號(hào)兩邊不可以有空格!例如,下面的變量聲明是錯(cuò)誤的 :
X = hello
在我所展示的例子中,引號(hào)并不都是必須的。只有當(dāng)變量值包含空格時(shí)才需要加上引號(hào)。例如:
X = hello world # 錯(cuò)誤
X = "hello world" # 正確
這是由于shell將每一行命令視為命令及其參數(shù)的集合,以空格分隔。 foo=bar就被視為一條命令。foo = bar 的問題就在于shell將空格分開的foo視為命令。同樣,X=hello world的問題就在于shell將X=hello視為一條完整的命令,而”world”則被徹底無(wú)視(因?yàn)橘x值命令不需其他參數(shù))。
單引號(hào) VS 雙引號(hào)
基本上來(lái)說(shuō),變量名會(huì)在雙引號(hào)中展開,單引號(hào)中則不會(huì)。如果你不需要引用變量值,那么使用單引號(hào)可以很直觀的輸出你期望的結(jié)果。 An example 示例
#!/bin/bash
echo -n '$USER=' # -n選項(xiàng)表示阻止echo換行
echo "$USER"
echo "$USER=$USER" # 該命令等價(jià)于上面的兩行命令
輸出如下(假設(shè)你的用戶名為elflord)) $USER=elflord $USER=elflord
$USER=elflord
$USER=elflord
從例子中可以看出,在雙引號(hào)中使用轉(zhuǎn)義字符也是一種解決方案。雖然雙引號(hào)的使用更靈活,但是其結(jié)果不可預(yù)見。如果要在單引號(hào)和雙引號(hào)之間做出選擇,最好選擇單引號(hào)。
使用引號(hào)封裝變量
有時(shí)候,使用雙引號(hào)來(lái)保護(hù)變量名是個(gè)很好的點(diǎn)子。如果你的變量值存在空格或者變量值為空字符串,這點(diǎn)就顯得尤其重要??聪旅孢@個(gè)例子:
#!/bin/bash
X=""
if [ -n $X ]; then # -n 用來(lái)檢查變量是否非空
echo "the variable X is not the empty string"
fi
運(yùn)行這個(gè)腳本,輸出如下:
the variable X is not the empty string
為何?這是因?yàn)閟hell將$X展開為空字符串,表達(dá)式[-n]返回真值(因?yàn)楦谋磉_(dá)式?jīng)]有提供參數(shù))。再看這個(gè)腳本:
#!/bin/bash
X=""
if [ -n "$X" ]; then # -n 用來(lái)檢查變量是否非空
echo "the variable X is not the empty string"
fi
在這個(gè)例子中,表達(dá)式展開為[ -n ""],由于引號(hào)中內(nèi)容為空,因此該表達(dá)式返回false值。
在執(zhí)行時(shí)展開變量
為了證實(shí)shell就像我上面說(shuō)的那樣直接展開變量,請(qǐng)看下面的例子:
#!/bin/bash
LS="ls"
LS_FLAGS="-al"
$LS $LS_FLAGS $HOME
乍一看可能有點(diǎn)不好理解。其實(shí)最后一行就是執(zhí)行這樣一條命令:
Ls -al /home/elflord
(假設(shè)當(dāng)前用戶home目錄為/home/elflord)。這就說(shuō)明了shell僅僅只是將變量替換為對(duì)應(yīng)的值再執(zhí)行命令而已。
使用大括號(hào)保護(hù)變量
這里有一個(gè)潛在的問題。假設(shè)你想打印變量X的值,并在值后面緊跟著打印”abc”。那么問題來(lái)了:你該怎么做呢? 先試一試:
#!/bin/bash
X=ABC
echo "$Xabc"
這個(gè)腳本沒有任何輸出。究竟哪里出了問題?這是由于shell以為我們想要打印變量Xabc的值,實(shí)際上卻沒有這個(gè)變量。為了解決這種問題可以用大括號(hào)將變量名包圍起來(lái),從而避免其他字符的影響。下面這個(gè)腳本可以正常工作:
!/bin/bashX=ABCecho “${X}abc”
#!/bin/bash
X=ABC
echo "${X}abc"
條件語(yǔ)句, if/then/elif
在某些情況下,我們需要做條件判斷。比如判斷字符串長(zhǎng)度是否為0?判斷文件foo是否存在?它是一個(gè)鏈接文件還是實(shí)際文件?首先,我們需要if命令來(lái)執(zhí)行檢查。語(yǔ)法如下:
if condition
then
statement1
statement2
..........
fi
當(dāng)指定條件不滿足時(shí),可以通過else來(lái)指定其他執(zhí)行動(dòng)作。
if condition
then
statement1
statement2
..........
else
statement3
fi
當(dāng)if條件不滿足時(shí),可以添加多個(gè)elif來(lái)檢查其他條件是否滿足。
if condition1
then
statement1
statement2
..........
elif condition2
then
statement3
statement4
........
elif condition3
then
statement5
statement6
........
fi
當(dāng)相關(guān)條件滿足時(shí),shell會(huì)執(zhí)行在相應(yīng)的if/elif與下個(gè)elif或fi之間的語(yǔ)句。事實(shí)上,判斷條件可以是任意命令,當(dāng)且只當(dāng)命令返回并且退出狀態(tài)為0時(shí),才會(huì)執(zhí)行該條件塊中的語(yǔ)句(換句話說(shuō),就是當(dāng)命令成功返回時(shí))。不過在本文的學(xué)習(xí)中,我們只會(huì)關(guān)注“test”或“[]”形式的條件判斷。
Test命令與操作符
條件判斷中的命令幾乎都是test命令。test根據(jù)測(cè)試條件通過或失敗來(lái)返回true或false(更準(zhǔn)確的說(shuō)是返回0或非0值)。如下所示:
test operand1 operator operand2
對(duì)某些測(cè)試來(lái)說(shuō),只需要一個(gè)操作數(shù)(operand2)通常是下面這種情況的簡(jiǎn)寫:
[ operand1 operator operand2 ]
為了讓我們的討論更接地氣一點(diǎn),給出下面一些例子:
#!/bin/bash
X=3
Y=4
empty_string=""
if [ $X -lt $Y ]# is $X less than $Y ?
then
echo "$X=${X}, which is smaller than $Y=${Y}"
fi
if [ -n "$empty_string" ]; then
echo "empty string is non_empty"
fi
if [ -e "${HOME}/.fvwmrc" ]; then # test to see if ~/.fvwmrc exists
echo "you have a .fvwmrc file"
if [ -L "${HOME}/.fvwmrc" ]; then # is it a symlink ?
echo "it's a symbolic link
elif [ -f "${HOME}/.fvwmrc" ]; then # is it a regular file ?
echo "it's a regular file"
fi
else
echo "you have no .fvwmrc file"
fi
需要注意的細(xì)節(jié)
Test命令的格式為“操作數(shù)< 空格 >操作符< 空格 >操作數(shù)”或者“操作符< 空格 >操作數(shù)”,這里特別說(shuō)明必須要有這些空格,因?yàn)閟hell將沒有空格的第一串字符視為一個(gè)操作符(以-開頭)或者操作數(shù)。比如下面這個(gè):
if [ 1=2 ]; then echo “hello”fi
它會(huì)打印出hello,這明顯與預(yù)期結(jié)果是不一致的(因?yàn)閟hell只看到操作數(shù)1=2,沒看到操作符)。
還有一種隱藏陷阱是未加引號(hào)的變量。像我們之前例子說(shuō)的-n測(cè)試時(shí)變量須加引號(hào)的情形。其實(shí),不管在什么情況下,加上引號(hào)總是沒有壞處的,還有可能規(guī)避一些很奇葩的錯(cuò)誤。因?yàn)橛袝r(shí)候不加引號(hào)的變量擴(kuò)展開的測(cè)試結(jié)果會(huì)讓人非常困惑。例如:
#!/bin/bash
X="-n"
Y=""
if [ $X = $Y ] ; then
echo "X=Y"
fi
這個(gè)腳本打印出來(lái)的結(jié)果是錯(cuò)誤的,因?yàn)閟hell將判斷展開為 [ -n = ],但是”=”的長(zhǎng)度不為0,所以條件判斷通過從而導(dǎo)致輸出結(jié)果為“X=Y”。
Test操作符簡(jiǎn)介
下圖是test操作符的快速查詢列表。當(dāng)然這個(gè)列表并不全面,但記下這些就足夠平常使用了(如果還需要了解其他操作符,可以查看man手冊(cè))。
operatorproduces true if…number of operands
-noperand non zero length1
-zoperand has zero length1
-dthere exists a directory whose name is operand1
-fthere exists a file whose name is operand1
-eqthe operands are integers and they are equal2
-neqthe opposite of -eq2
=the operands are equal (as strings)2
!=opposite of =2
-ltoperand1 is strictly less than operand2 (both operands should be integers)2
-gtoperand1 is strictly greater than operand2 (both operands should be integers)2
-geoperand1 is greater than or equal to operand2 (both operands should be integers)2
-leoperand1 is less than or equal to operand2 (both operands should be integers)2
循環(huán)
循環(huán)結(jié)構(gòu)允許我們執(zhí)行重復(fù)的步驟或者在若干個(gè)不同條目上執(zhí)行相同的程序。Bash中有下面兩種循環(huán)
for 循環(huán)
while 循環(huán)
For 循環(huán)
直接來(lái)個(gè)例子,來(lái)直觀地感受for循環(huán)的語(yǔ)法。
#!/bin/bash
for X in red green blue
do
echo $X
done
For循環(huán)會(huì)遍歷空格分開的條目。注意,如果某一項(xiàng)含有空格,必須要用引號(hào)引起來(lái),例子如下:
#!/bin/bash
colour1="red"
colour2="light blue"
colour3="dark green"
for X in "$colour1" $colour2" $colour3"
do
echo $X
done
如果我們漏掉for循環(huán)中的引號(hào),你能猜想出會(huì)發(fā)生什么嗎?這個(gè)例子說(shuō)明,除非你確認(rèn)變量中不會(huì)包含空格,否則最好都用引號(hào)將變量保護(hù)起來(lái)。
在for循環(huán)中使用通配符
如果shell解析字符串時(shí)遇到*號(hào),會(huì)將它展開為所有匹配的文件名。當(dāng)且僅當(dāng)目標(biāo)文件與號(hào)展開后的字符串一致才會(huì)匹配成功。例如,單獨(dú)的*號(hào)展開為當(dāng)前目錄的所有文件,中間以空格分開(包含隱藏文件)。
所以:
echo *
列出當(dāng)前目錄下的所有文件和目錄。
echo *.jpg
列出所有的jpeg圖片格式的文件。
echo ${HOME}/public_html/*.jpg
列出home目錄中public_html目錄下的所有jpeg文件。
正是由于這種特性,使得我們可以很方便的來(lái)操作目錄和文件,尤其是和for循環(huán)結(jié)合使用時(shí),更是便利。例子如下:
#!/bin/bash
for X in *.html
do
grep -L '<UL>' "$X"
done
打印出當(dāng)前目錄下所有不包含<UL>字段的html文件。
While 循環(huán)
當(dāng)給定條件為真值時(shí),while循環(huán)會(huì)重復(fù)執(zhí)行。例如:
#!/bin/bash
X=0
while [ $X -le 20 ]
do
echo $X
X=$((X+1))
done
這樣導(dǎo)致這樣的疑問: 為什么bash不能使用C風(fēng)格的for循環(huán)呢?
for (X=1,X<10; X++)
這也跟bash自身的特性有關(guān),之所以不允許這種for循環(huán)是由于:bash是一種解釋性語(yǔ)言,因此其運(yùn)行效率比較低。也正是由于這個(gè)原因,高負(fù)荷迭代是不允許的。
命令替換
Bash shell有個(gè)非常好用的特性叫做命令替換。允許我們將一個(gè)命令的輸出當(dāng)做另一個(gè)命令的輸入。比如你想要將命令的輸出賦值給變量X,你可以通過變量替換來(lái)實(shí)現(xiàn)。
有兩種命令替換的方式:大括號(hào)擴(kuò)展和反撇號(hào)擴(kuò)展。
大括號(hào)擴(kuò)展: $(commands) 會(huì)展開為命令commands的輸出結(jié)果。并且允許嵌套使用,所以commands中允許包含子大括號(hào)擴(kuò)展。
反撇好擴(kuò)展:將commands擴(kuò)展為命令commands的輸出結(jié)果。不允許嵌套。
這里有一個(gè)例子:
#!/bin/bash
files="$(ls)"
web_files=`ls public_html`
echo "$files" # we need the quotes to preserve embedded newlines in $files
echo "$web_files" # we need the quotes to preserve newlines
X=`expr 3 * 2 + 4` # expr evaluate arithmatic expressions. man expr for details.
echo "$X"
$()替換方式的優(yōu)點(diǎn)不言自明:非常易于嵌套。并且大多數(shù)bourne shell的衍生版本都支持(POSIX shell 或者更好的都支持)。不過,反撇號(hào)替換更簡(jiǎn)單明了,即使是最基本的shell它也提供了支持(任意版本的#!/bin/sh都可以)。
更多信息請(qǐng)查看IT技術(shù)專欄