簡(jiǎn)單shell腳本
!/bin/bash
這一行表明,不管用戶選擇的是那種交互式shell,該腳本需要使用bash shell來運(yùn)行。由于每種shell的語法大不相同,所以這句非常重要。
簡(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)變量,無需在腳本中定義即可使用。請(qǐng)注意,在雙引號(hào)中引用的變量會(huì)被展開(expanded)?!癳xpanded”是一個(gè)非常合適的形容詞:基本上,當(dāng)shell執(zhí)行命令并遇到$USER變量時(shí),會(huì)將其替換為該變量對(duì)應(yīng)的值。
變量
任何編程語言都會(huì)用到變量。你可以使用下面的語句來定義一個(gè)變量:
X="hello"
并按下面的格式來引用這個(gè)變量:
$X
更具體的說,$X表示變量X的值。關(guān)于語義方面有如下幾點(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”則被徹底無視(因?yàn)橘x值命令不需其他參數(shù))。
單引號(hào) VS 雙引號(hào)
基本上來說,變量名會(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)來保護(hù)變量名是個(gè)很好的點(diǎn)子。如果你的變量值存在空格或者變量值為空字符串,這點(diǎn)就顯得尤其重要??聪旅孢@個(gè)例子:
#!/bin/bash
X=""
if [ -n $X ]; then # -n 用來檢查變量是否非空
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 用來檢查變量是否非空
echo "the variable X is not the empty string"
fi
在這個(gè)例子中,表達(dá)式展開為[ -n ""],由于引號(hào)中內(nèi)容為空,因此該表達(dá)式返回false值。
在執(zhí)行時(shí)展開變量
為了證實(shí)shell就像我上面說的那樣直接展開變量,請(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)。這就說明了shell僅僅只是將變量替換為對(duì)應(yīng)的值再執(zhí)行命令而已。
使用大括號(hào)保護(hù)變量
這里有一個(gè)潛在的問題。假設(shè)你想打印變量X的值,并在值后面緊跟著打印”abc”。那么問題來了:你該怎么做呢? 先試一試:
#!/bin/bash
X=ABC
echo "$Xabc"
這個(gè)腳本沒有任何輸出。究竟哪里出了問題?這是由于shell以為我們想要打印變量Xabc的值,實(shí)際上卻沒有這個(gè)變量。為了解決這種問題可以用大括號(hào)將變量名包圍起來,從而避免其他字符的影響。下面這個(gè)腳本可以正常工作:
!/bin/bashX=ABCecho “${X}abc”
#!/bin/bash
X=ABC
echo "${X}abc"
條件語句, if/then/elif
在某些情況下,我們需要做條件判斷。比如判斷字符串長(zhǎng)度是否為0?判斷文件foo是否存在?它是一個(gè)鏈接文件還是實(shí)際文件?首先,我們需要if命令來執(zhí)行檢查。語法如下:
if condition
then
statement1
statement2
..........
fi
當(dāng)指定條件不滿足時(shí),可以通過else來指定其他執(zhí)行動(dòng)作。
if condition
then
statement1
statement2
..........
else
statement3
fi
當(dāng)if條件不滿足時(shí),可以添加多個(gè)elif來檢查其他條件是否滿足。
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之間的語句。事實(shí)上,判斷條件可以是任意命令,當(dāng)且只當(dāng)命令返回并且退出狀態(tài)為0時(shí),才會(huì)執(zhí)行該條件塊中的語句(換句話說,就是當(dāng)命令成功返回時(shí))。不過在本文的學(xué)習(xí)中,我們只會(huì)關(guān)注“test”或“[]”形式的條件判斷。
Test命令與操作符
條件判斷中的命令幾乎都是test命令。test根據(jù)測(cè)試條件通過或失敗來返回true或false(更準(zhǔn)確的說是返回0或非0值)。如下所示:
test operand1 operator operand2
對(duì)某些測(cè)試來說,只需要一個(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ù)”,這里特別說明必須要有這些空格,因?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)的變量。像我們之前例子說的-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è)腳本打印出來的結(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)
直接來個(gè)例子,來直觀地感受for循環(huán)的語法。
#!/bin/bash
for X in red green blue
do
echo $X
done
For循環(huán)會(huì)遍歷空格分開的條目。注意,如果某一項(xiàng)含有空格,必須要用引號(hào)引起來,例子如下:
#!/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è)例子說明,除非你確認(rèn)變量中不會(huì)包含空格,否則最好都用引號(hào)將變量保護(hù)起來。
在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文件。
正是由于這種特性,使得我們可以很方便的來操作目錄和文件,尤其是和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ùn)行效率比較低。也正是由于這個(gè)原因,高負(fù)荷迭代是不允許的。
命令替換
Bash shell有個(gè)非常好用的特性叫做命令替換。允許我們將一個(gè)命令的輸出當(dāng)做另一個(gè)命令的輸入。比如你想要將命令的輸出賦值給變量X,你可以通過變量替換來實(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ù)專欄