国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
Shell腳本

Shell腳本


1. Shell的歷史

Shell的作用是解釋執(zhí)行用戶的命令,用戶輸入一條命令,Shell就解釋執(zhí)行一條,這種方式稱為交互式(Interactive),Shell還有一種執(zhí)行命令的方式稱為批處理(Batch),用戶事先寫一個(gè)Shell腳本(Script),其中有很多條命令,讓Shell一次把這些命令執(zhí)行完,而不必一條一條地敲命令。Shell腳本和編程語言很相似,也有變量和流程控制語句,但Shell腳本是解釋執(zhí)行的,不需要編譯,Shell程序從腳本中一行一行讀取并執(zhí)行這些命令,相當(dāng)于一個(gè)用戶把腳本中的命令一行一行敲到Shell提示符下執(zhí)行。

由于歷史原因,UNIX系統(tǒng)上有很多種Shell:

  1. sh(Bourne Shell):由Steve Bourne開發(fā),各種UNIX系統(tǒng)都配有sh

  2. csh(C Shell):由Bill Joy開發(fā),隨BSD UNIX發(fā)布,它的流程控制語句很像C語言,支持很多Bourne Shell所不支持的功能:作業(yè)控制,命令歷史,命令行編輯。

  3. ksh(Korn Shell):由David Korn開發(fā),向后兼容sh的功能,并且添加了csh引入的新功 能,是目前很多UNIX系統(tǒng)標(biāo)準(zhǔn)配置的Shell,在這些系統(tǒng)上/bin/sh往往是指向/bin/ksh的符號(hào)鏈接。

  4. tcsh(TENEX C Shell):是csh的 增強(qiáng)版本,引入了命令補(bǔ)全等功能,在FreeBSD、Mac OS X等系統(tǒng)上替代了csh。

  5. bash(Bourne Again Shell):由GNU開發(fā)的Shell,主要目標(biāo)是與POSIX標(biāo)準(zhǔn)保持一致,同時(shí)兼顧對(duì)sh的 兼容,bashcshksh借鑒了很多功能,是各種Linux發(fā)行版標(biāo)準(zhǔn)配置的Shell,在Linux系統(tǒng)上/bin/sh往往是指向/bin/bash的 符號(hào)鏈接[38]。雖然如此,bashsh還是有很多不同的,一方面,bash擴(kuò) 展了一些命令和參數(shù),另一方面,bash并不完全和sh兼容,有些行為并不一致,所以bash需 要模擬sh的行為:當(dāng)我們通過sh這 個(gè)程序名啟動(dòng)bash時(shí),bash可 以假裝自己是sh,不認(rèn)擴(kuò)展的命令,并且行為與sh保 持一致。

文件/etc/shells給出了系統(tǒng)中所有已知(不一定已安裝)的Shell,除了上面提到的Shell之外還有很多變種。

# /etc/shells: valid login shells
/bin/csh
/bin/sh
/usr/bin/es
/usr/bin/ksh
/bin/ksh
/usr/bin/rc
/usr/bin/tcsh
/bin/tcsh
/usr/bin/esh
/bin/dash
/bin/bash
/bin/rbash
/usr/bin/screen

用戶的默認(rèn)Shell設(shè)置在/etc/passwd文件中,例如下面這行對(duì)用戶mia的設(shè)置:

mia:L2NOfqdlPrHwE:504:504:Mia Maya:/home/mia:/bin/bash

用戶mia從字符終端登錄或者打開圖形終端窗口時(shí)就會(huì)自動(dòng)執(zhí)行/bin/bash。如果要切換到其它Shell,可以在命令行輸入程序名,例如:

~$ sh(在bash提示符下輸入sh命令)
$(出現(xiàn)sh的提示符)
$(按Ctrl-d或者輸入exit命令)
~$(回到bash提示符)
~$(再次按Ctrl-d或者輸入exit命令會(huì)退出登錄或者關(guān)閉圖形終端窗口)

本章只介紹bashsh的用法和相關(guān)語法,不介紹其它Shell。所以下文提到Shell都是指bashsh。



[38]最新的發(fā)行版有一些變化,例如Ubuntu 7.10的/bin/sh是指向/bin/dash的符號(hào)鏈接,dash也是一種類似bash的Shell。

$ ls /bin/sh /bin/dash -l
-rwxr-xr-x 1 root root 79988 2008-03-12 19:22 /bin/dash
lrwxrwxrwx 1 root root 4 2008-07-04 05:58 /bin/sh -> dash

2. Shell如何執(zhí)行命令

2.1. 執(zhí)行交互式命令

用戶在命令行輸入命令后,一般情況下Shell會(huì)forkexec該命令,但是Shell的內(nèi)建命令例外,執(zhí)行內(nèi)建命令相當(dāng)于調(diào)用Shell進(jìn)程中的一個(gè)函數(shù),并不創(chuàng)建新的進(jìn)程。以前學(xué)過的cd、alias、umask、exit等命令即是內(nèi)建命令,凡是用which命令查不到程序文件所在位置的命令都是內(nèi)建命令,內(nèi)建命令沒有單獨(dú)的man手冊(cè),要在man手冊(cè)中查看內(nèi)建命令,應(yīng)該

$ man bash-builtins

本節(jié)會(huì)介紹很多內(nèi)建命令,如export、shiftif、eval[for、while等等。內(nèi)建命令雖然不創(chuàng)建新的進(jìn)程,但也會(huì)有ExitStatus,通常也用0表示成功非零表示失敗,雖然內(nèi)建命令不創(chuàng)建新的進(jìn)程,但執(zhí)行結(jié)束后也會(huì)有一個(gè)狀態(tài)碼,也可以用特殊變量$?讀出。

2.2. 執(zhí)行腳本

首先編寫一個(gè)簡(jiǎn)單的腳本,保存為script.sh

例 31.1. 簡(jiǎn)單的Shell腳本

#! /bin/sh

cd ..
ls

Shell腳本中用#表示注釋,相當(dāng)于C語言的//注釋。但如果#位于第一行開頭,并且是#!(稱為Shebang)則例外,它表示該腳本使用后面指定的解釋器/bin/sh解釋執(zhí)行。如果把這個(gè)腳本文件加上可執(zhí)行權(quán)限然后執(zhí)行:

$ chmod +x script.sh
$ ./script.sh

Shell會(huì)fork一個(gè)子進(jìn)程并調(diào)用exec執(zhí)行./script.sh這個(gè)程序,exec系統(tǒng)調(diào)用應(yīng)該把子進(jìn)程的代碼段替換成./script.sh程序的代碼段,并從它的_start開始執(zhí)行。然而script.sh是個(gè)文本文件,根本沒有代碼段和_start函數(shù),怎么辦呢?其實(shí)exec還有另外一種機(jī)制,如果要執(zhí)行的是一個(gè)文本文件,并且第一行用Shebang指定了解釋器,則用解釋器程序的代碼段替換當(dāng)前進(jìn)程,并且從解釋器的_start開始執(zhí)行,而這個(gè)文本文件被當(dāng)作命令行參數(shù)傳給解釋器。因此,執(zhí)行上述腳本相當(dāng)于執(zhí)行程序

$ /bin/sh ./script.sh

以這種方式執(zhí)行不需要script.sh文件具有可執(zhí)行權(quán)限。再舉個(gè)例子,比如某個(gè)sed腳本的文件名是script,它的開頭是

#! /bin/sed -f

執(zhí)行./script相當(dāng)于執(zhí)行程序

$ /bin/sed -f ./script.sh

以上介紹了兩種執(zhí)行Shell腳本的方法:

$ ./script.sh
$ sh ./script.sh

這兩種方法本質(zhì)上是一樣的,執(zhí)行上述腳本的步驟為:

圖 31.1. Shell腳本的執(zhí)行過程


  1. 交互Shell(bashfork/exec一個(gè)子Shell(sh)用于執(zhí)行腳本,父進(jìn)程bash等待 子進(jìn)程sh終止。

  2. sh讀取腳本中的cd ..命令,調(diào)用相應(yīng) 的函數(shù)執(zhí)行內(nèi)建命令,改變當(dāng)前工作目錄為上一級(jí)目錄。

  3. sh讀 取腳本中的ls命令,fork/exec這個(gè)程序,列出當(dāng)前工作目錄下的文件,sh等 待ls終止。

  4. ls終 止后,sh繼續(xù)執(zhí)行,讀到腳本文件末尾,sh終 止。

  5. sh終止后,bash繼續(xù)執(zhí)行,打印提示符等待用戶輸入。

如果將命令行下輸入的命令用()括號(hào)括起來,那么也會(huì)fork出一個(gè)子Shell執(zhí)行小括號(hào)中的命令,一行中可以輸入由分號(hào);隔開的多個(gè)命令,比如:

$ (cd ..;ls -l)

和上面兩種方法執(zhí)行Shell腳本的效果是相同的,cd ..命令改變的是子Shell的PWD,而不會(huì)影響到交互式Shell。然而命令

$ cd ..;ls -l

則有不同的效果,cd ..命令是直接在交互式Shell下執(zhí)行的,改變交互式Shell的PWD,然而這種方式相當(dāng)于這樣執(zhí)行Shell腳本:

$ source ./script.sh

或者

$ . ./script.sh

source或者.命令是Shell的內(nèi)建命令,這種方式也不會(huì)創(chuàng)建子Shell,而是直接在交互式Shell下逐行執(zhí)行腳本中的命令。



3. Shell的基本語法

3.1. 變量

按照慣例,Shell變量由全大寫字母加下劃線組成,有兩種類型的Shell變量:

環(huán)境變量

第 2 節(jié)“環(huán)境變量”中講過,環(huán)境變量可以從父進(jìn)程傳給子進(jìn)程,因此Shell進(jìn)程的環(huán)境變量可以從當(dāng)前Shell進(jìn)程傳給fork出來的子進(jìn)程。用printenv命令可以顯示當(dāng)前Shell進(jìn)程的環(huán)境變量。

本地變量

只存在于當(dāng)前Shell進(jìn)程,用set命令可以顯示當(dāng)前Shell進(jìn)程中定義的所有變量(包括本地變量和環(huán)境變量)和函數(shù)。

環(huán)境變量是任何進(jìn)程都有的概念,而本地變量是Shell特有的概念。在Shell中,環(huán)境變量和本地變量的定義和用法相似。在Shell中定義或賦值一個(gè)變量:

$ VARNAME=value

注意等號(hào)兩邊都不能有空格,否則會(huì)被Shell解釋成命令和命令行參數(shù)。

一個(gè)變量定義后僅存在于當(dāng)前Shell進(jìn)程,它是本地變量,用export命令可以把本地變量導(dǎo)出為環(huán)境變量,定義和導(dǎo)出環(huán)境變量通??梢砸徊酵瓿桑?/p>

$ export VARNAME=value

也可以分兩步完成:

$ VARNAME=value
$ export VARNAME

unset命令可以刪除已定義的環(huán)境變量或本地變量。

$ unset VARNAME

如果一個(gè)變量叫做VARNAME,用${VARNAME}可以表示它的值,在不引起歧義的情況下也可以用$VARNAME表示它的值。通過以下例子比較這兩種表示法的不同:

$ echo $SHELL
$ echo $SHELLabc
$ echo $SHELL abc
$ echo ${SHELL}abc

注意,在定義變量時(shí)不用$,取變量值時(shí)要用$。和C語言不同的是,Shell變量不需要明確定義類型,事實(shí)上Shell變量的值都是字符串,比如我們定義VAR=45,其實(shí)VAR的值是字符串45而非整數(shù)。Shell變量不需要先定義后使用,如果對(duì)一個(gè)沒有定義的變量取值,則值為空字符串。

3.2. 文件名代換(Globbing):* ? []

這些用于匹配的字符稱為通配符(Wildcard),具體如下:

表 31.1. 通配符

* 匹 配0個(gè)或多個(gè)任意字符
匹配一個(gè)任意字符
[若干字符] 匹 配方括號(hào)中任意一個(gè)字符的一次出現(xiàn)

$ ls /dev/ttyS*
$ ls ch0?.doc
$ ls ch0[0-2].doc
$ ls ch[012][0-9].doc

注意,Globbing所匹配的文件名是由Shell展開的,也就是說在參數(shù)還沒傳給程序之前已經(jīng)展開了,比如上述ls ch0[012].doc命令,如果當(dāng)前目錄下有ch00.docch02.doc,則傳給ls命令的參數(shù)實(shí)際上是這兩個(gè)文件名,而不是一個(gè)匹配字符串。

3.3. 命令代換:`或 $()

由反引號(hào)括起來的也是一條命令,Shell先執(zhí)行該命令,然后將輸出結(jié)果立刻代換到當(dāng)前命令行中。例如定義一個(gè)變量存放date命令的輸出:

$ DATE=`date`
$ echo $DATE

命令代換也可以用$()表示:

$ DATE=$(date)

3.4. 算術(shù)代換:$(())

用于算術(shù)計(jì)算,$(())中的Shell變量取值將轉(zhuǎn)換成整數(shù),例如:

$ VAR=45
$ echo $(($VAR+3))

$(())中只能用+-*/和()運(yùn)算符,并且只能做整數(shù)運(yùn)算。

3.5. 轉(zhuǎn)義字符\

和C語言類似,\在Shell中被用作轉(zhuǎn)義字符,用于去除緊跟其后的單個(gè)字符的特殊意義(回車除外),換句話說,緊跟其后的字符取字面值。例如:

$ echo $SHELL
/bin/bash
$ echo \$SHELL
$SHELL
$ echo \\
\

比如創(chuàng)建一個(gè)文件名為“$ $”的文件可以這樣:

$ touch \$\ \$

還有一個(gè)字符雖然不具有特殊含義,但是要用它做文件名也很麻煩,就是-號(hào)。如果要?jiǎng)?chuàng)建一個(gè)文件名以-號(hào)開頭的文件,這樣是不行的:

$ touch -hello
touch: invalid option -- h
Try `touch --help' for more information.

即使加上\轉(zhuǎn)義也還是報(bào)錯(cuò):

$ touch \-hello
touch: invalid option -- h
Try `touch --help' for more information.

因?yàn)楦鞣NUNIX命令都把-號(hào)開頭的命令行參數(shù)當(dāng)作命令的選項(xiàng),而不會(huì)當(dāng)作文件名。如果非要處理以-號(hào)開頭的文件名,可以有兩種辦法:

$ touch ./-hello

或者

$ touch -- -hello

\還有一種用法,在\后敲回車表示續(xù)行,Shell并不會(huì)立刻執(zhí)行命令,而是把光標(biāo)移到下一行,給出一個(gè)續(xù)行提示符>,等待用戶繼續(xù)輸入,最后把所有的續(xù)行接到一起當(dāng)作一個(gè)命令執(zhí)行。例如:

$ ls \
> -l
(ls -l命令的輸出)

3.6. 單引號(hào)

和C語言不一樣,Shell腳本中的單引號(hào)和雙引號(hào)一樣都是字符串的界定符(雙引號(hào)下一節(jié)介紹),而不是字符的界定符。單引號(hào)用于保持引號(hào)內(nèi)所有字符的字面值,即使引號(hào)內(nèi)的\和回車也不例外,但是字符串中不能出現(xiàn)單引號(hào)。如果引號(hào)沒有配對(duì)就輸入回車,Shell會(huì)給出續(xù)行提示符,要求用戶把引號(hào)配上對(duì)。例如:

$ echo '$SHELL'
$SHELL
$ echo 'ABC\(回車)
> DE'(再按一次回車結(jié)束命令)
ABC\
DE

3.7. 雙引號(hào)

雙引號(hào)用于保持引號(hào)內(nèi)所有字符的字面值(回車也不例外),但以下情況除外:

  • $加變量名可 以取變量的值

  • 反引號(hào)仍表示命令替換

  • \$表示$的字面值

  • \` 表示`的字面值

  • \"表示"的字面值

  • \\表示\的字面值

  • 除 以上情況之外,在其它字符前面的\無特殊含義,只表示字面值

$ echo "$SHELL"
/bin/bash
$ echo "`date`"
Sun Apr 20 11:22:06 CEST 2003
$ echo "I'd say: \"Go for it\""
I'd say: "Go for it"
$ echo "\"(回車)
>"(再按一次回車結(jié)束命令)
"

$ echo "\\"
\

4. bash啟動(dòng)腳本

啟動(dòng)腳本是bash啟動(dòng)時(shí)自動(dòng)執(zhí)行的腳本。用戶可以把一些環(huán)境變量的設(shè)置和alias、umask設(shè)置放在啟動(dòng)腳本中,這樣每次啟動(dòng)Shell時(shí)這些設(shè)置都自動(dòng)生效。思考一下,bash在執(zhí)行啟動(dòng)腳本時(shí)是以fork子Shell方式執(zhí)行的還是以source方式執(zhí)行的?

啟動(dòng)bash的方法不同,執(zhí)行啟動(dòng)腳本的步驟也不相同,具體可分為以下幾種情況。

4.1. 作為交互登錄Shell啟動(dòng),或者使用--login參數(shù)啟動(dòng)

交互Shell是指用戶在提示符下輸命令的Shell而非執(zhí)行腳本的Shell,登錄Shell就是在輸入用戶名和密碼登錄后得到的Shell,比如從字符終端登錄或者用telnet/ssh從遠(yuǎn)程登錄,但是從圖形界面的窗口管理器登錄之后會(huì)顯示桌面而不會(huì)產(chǎn)生登錄Shell(也不會(huì)執(zhí)行啟動(dòng)腳本),在圖形界面下打開終端窗口得到的Shell也不是登錄Shell。

這樣啟動(dòng)bash會(huì)自動(dòng)執(zhí)行以下腳本:

  1. 首先執(zhí)行/etc/profile, 系統(tǒng)中每個(gè)用戶登錄時(shí)都要執(zhí)行這個(gè)腳本,如果系統(tǒng)管理員希望某個(gè)設(shè)置對(duì)所有用戶都生效,可以寫在這個(gè)腳本里

  2. 然后依次查找 當(dāng)前用戶主目錄的~/.bash_profile、~/.bash_login~/.profile三 個(gè)文件,找到第一個(gè)存在并且可讀的文件來執(zhí)行,如果希望某個(gè)設(shè)置只對(duì)當(dāng)前用戶生效,可以寫在這個(gè)腳本里,由于這個(gè)腳本在/etc/profile之后執(zhí)行,/etc/profile設(shè) 置的一些環(huán)境變量的值在這個(gè)腳本中可以修改,也就是說,當(dāng)前用戶的設(shè)置可以覆蓋(Override)系統(tǒng)中全局的設(shè)置。~/.profile這個(gè) 啟動(dòng)腳本是sh規(guī)定的,bash規(guī) 定首先查找以~/.bash_開頭的啟動(dòng)腳本,如果沒有則執(zhí)行~/.profile,是為了和sh保持一 致。

  3. 順便一提,在退出登錄時(shí)會(huì)執(zhí)行~/.bash_logout腳 本(如果它存在的話)。

4.2. 以交互非登錄Shell啟動(dòng)

比如在圖形界面下開一個(gè)終端窗口,或者在登錄Shell提示符下再輸入bash命令,就得到一個(gè)交互非登錄的Shell,這種Shell在啟動(dòng)時(shí)自動(dòng)執(zhí)行~/.bashrc腳本。

為了使登錄Shell也能自動(dòng)執(zhí)行~/.bashrc,通常在~/.bash_profile中調(diào)用~/.bashrc

if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi

這幾行的意思是如果~/.bashrc文件存在則source它。多數(shù)Linux發(fā)行版在創(chuàng)建帳戶時(shí)會(huì)自動(dòng)創(chuàng)建~/.bash_profile~/.bashrc腳本,~/.bash_profile中通常都有上面這幾行。所以,如果要在啟動(dòng)腳本中做某些設(shè)置,使它在圖形終端窗口和字符終端的Shell中都起作用,最好就是在~/.bashrc中設(shè)置。

下面做一個(gè)實(shí)驗(yàn),在~/.bashrc文件末尾添加一行(如果這個(gè)文件不存在就創(chuàng)建它):

export PATH=$PATH:/home/akaedu

然后關(guān)掉終端窗口重新打開,或者從字符終端logout之后重新登錄,現(xiàn)在主目錄下的程序應(yīng)該可以直接輸程序名運(yùn)行而不必輸入路徑了,例如:

~$ a.out

就可以了,而不必

~$ ./a.out

為什么登錄Shell和非登錄Shell的啟動(dòng)腳本要區(qū)分開呢?最初的設(shè)計(jì)是這樣考慮的,如果從字符終端或者遠(yuǎn)程登錄,那么登錄Shell是該用戶的所有其它進(jìn)程的父進(jìn)程,也是其它子Shell的父進(jìn)程,所以環(huán)境變量在登錄Shell的啟動(dòng)腳本里設(shè)置一次就可以自動(dòng)帶到其它非登錄Shell里,而Shell的本地變量、函數(shù)、alias等設(shè)置沒有辦法帶到子Shell里,需要每次啟動(dòng)非登錄Shell時(shí)設(shè)置一遍,所以就需要有非登錄Shell的啟動(dòng)腳本,所以一般來說在~/.bash_profile里設(shè)置環(huán)境變量,在~/.bashrc里設(shè)置本地變量、函數(shù)、alias等。如果你的Linux帶有圖形系統(tǒng)則不能這樣設(shè)置,由于從圖形界面的窗口管理器登錄并不會(huì)產(chǎn)生登錄Shell,所以環(huán)境變量也應(yīng)該在~/.bashrc里設(shè)置。

4.3. 非交互啟動(dòng)

為執(zhí)行腳本而fork出來的子Shell是非交互Shell,啟動(dòng)時(shí)執(zhí)行的腳本文件由環(huán)境變量BASH_ENV定義,相當(dāng)于自動(dòng)執(zhí)行以下命令:

if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi

如果環(huán)境變量BASH_ENV的值不是空字符串,則把它的值當(dāng)作啟動(dòng)腳本的文件名,source這個(gè)腳本。

4.4. 以sh命令啟動(dòng)

如果以sh命令啟動(dòng)bash,bash將模擬sh的行為,以~/.bash_開頭的那些啟動(dòng)腳本就不認(rèn)了。所以,如果作為交互登錄Shell啟動(dòng),或者使用--login參數(shù)啟動(dòng),則依次執(zhí)行以下腳本:

  1. /etc/profile

  2. ~/.profile

如果作為交互Shell啟動(dòng),相當(dāng)于自動(dòng)執(zhí)行以下命令:

if [ -n "$ENV" ]; then . "$ENV"; fi

如果作為非交互Shell啟動(dòng),則不執(zhí)行任何啟動(dòng)腳本。通常我們寫的Shell腳本都以#! /bin/sh開頭,都屬于這種方式。


5. Shell腳本語法

5.1. 條件測(cè)試:test [

命令test[可以測(cè)試一個(gè)條件是否成立,如果測(cè)試結(jié)果為真,則該命令的ExitStatus為0,如果測(cè)試結(jié)果為假,則命令的Exit Status為1(注意與C語言的邏輯表示正好相反)。例如測(cè)試兩個(gè)數(shù)的大小關(guān)系:

$ VAR=2
$ test $VAR -gt 1
$ echo $?
0
$ test $VAR -gt 3
$ echo $?
1
$ [ $VAR -gt 3 ]
$ echo $?
1

雖然看起來很奇怪,但左方括號(hào)[確實(shí)是一個(gè)命令的名字,傳給命令的各參數(shù)之間應(yīng)該用空格隔開,比如,$VAR、-gt3、][命令的四個(gè)參數(shù),它們之間必須用空格隔開。命令test[的參數(shù)形式是相同的,只不過test命令不需要]參數(shù)。以[命令為例,常見的測(cè)試命令如下表所示:

表 31.2. 測(cè)試命令

[ -d DIR ] 如果DIR存 在并且是一個(gè)目錄則為真
[ -f FILE ] 如 果FILE存在且是一個(gè)普通文件則為真
[ -z STRING ] 如果STRING的 長(zhǎng)度為零則為真
[ -n STRING ] 如 果STRING的長(zhǎng)度非零則為真
[ STRING1 = STRING2 ] 如果兩個(gè)字符串相同則為真
[ STRING1 != STRING2 ] 如果字符串不相同則為真
[ ARG1 OP ARG2 ] ARG1ARG2應(yīng)該是整數(shù)或者取值為整數(shù)的變量,OP-eq(等于)-ne(不等于)-lt(小于)-le(小于等于)-gt(大于)-ge(大于等于)之中的 一個(gè)

和C語言類似,測(cè)試條件之間還可以做與、或、非邏輯運(yùn)算:

表 31.3. 帶與、或、非的測(cè)試命令

[ ! EXPR ] EXPR可 以是上表中的任意一種測(cè)試條件,!表示邏輯反
[ EXPR1 -a EXPR2 ] EXPR1EXPR2可以是上表中的任意一種測(cè)試條件,-a表 示邏輯與
[ EXPR1 -o EXPR2 ] EXPR1EXPR2可以是上表中的任 意一種測(cè)試條件,-o表示邏輯或

例如:

$ VAR=abc
$ [ -d Desktop -a $VAR = 'abc' ]
$ echo $?
0

注意,如果上例中的$VAR變量事先沒有定義,則被Shell展開為空字符串,會(huì)造成測(cè)試條件的語法錯(cuò)誤(展開為[ -d Desktop -a = 'abc' ]),作為一種好的Shell編程習(xí)慣,應(yīng)該總是把變量取值放在雙引號(hào)之中(展開為[ -d Desktop-a "" = 'abc' ]):

$ unset VAR
$ [ -d Desktop -a $VAR = 'abc' ]
bash: [: too many arguments
$ [ -d Desktop -a "$VAR" = 'abc' ]
$ echo $?
1

5.2. if/then/elif/else/fi

和C語言類似,在Shell中用if、then、elifelse、fi這幾條命令實(shí)現(xiàn)分支控制。這種流程控制語句本質(zhì)上也是由若干條Shell命令組成的,例如先前講過的

if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi

其實(shí)是三條命令,if [ -f ~/.bashrc ]是第一條,then . ~/.bashrc是第二條,fi是第三條。如果兩條命令寫在同一行則需要用;號(hào)隔開,一行只寫一條命令就不需要寫;號(hào)了,另外,then后面有換行,但這條命令沒寫完,Shell會(huì)自動(dòng)續(xù)行,把下一行接在then后面當(dāng)作一條命令處理。和[命令一樣,要注意命令和各參數(shù)之間必須用空格隔開。if命令的參數(shù)組成一條子命令,如果該子命令的Exit Status為0(表示真),則執(zhí)行then后面的子命令,如果Exit Status非0(表示假),則執(zhí)行elif、else或者fi后面的子命令。if后面的子命令通常是測(cè)試命令,但也可以是其它命令。Shell腳本沒有{}括號(hào),所以用fi表示if語句塊的結(jié)束。見下例:

#! /bin/sh

if [ -f /bin/bash ]
then echo "/bin/bash is a file"
else echo "/bin/bash is NOT a file"
fi
if :; then echo "always true"; fi

:是一個(gè)特殊的命令,稱為空命令,該命令不做任何事,但Exit Status總是真。此外,也可以執(zhí)行/bin/true/bin/false得到真或假的Exit Status。再看一個(gè)例子:

#! /bin/sh

echo "Is it morning? Please answer yes or no."
read YES_OR_NO
if [ "$YES_OR_NO" = "yes" ]; then
echo "Good morning!"
elif [ "$YES_OR_NO" = "no" ]; then
echo "Good afternoon!"
else
echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
exit 1
fi
exit 0

上例中的read命令的作用是等待用戶輸入一行字符串,將該字符串存到一個(gè)Shell變量中。

此外,Shell還提供了&&和||語法,和C語言類似,具有Short-circuit特性,很多Shell腳本喜歡寫成這樣:

test "$(whoami)" != 'root' && (echo you are using a non-privileged account; exit 1)

&&相當(dāng)于“if...then...”,而||相當(dāng)于“ifnot...then...”。&&和||用于連接兩個(gè)命令,而上面講的-a-o僅用于在測(cè)試表達(dá)式中連接兩個(gè)測(cè)試條件,要注意它們的區(qū)別,例如,

test "$VAR" -gt 1 -a "$VAR" -lt 3

和以下寫法是等價(jià)的

test "$VAR" -gt 1 && test "$VAR" -lt 3

5.3. case/esac

case命令可類比C語言的switch/case語句,esac表示case語句塊的結(jié)束。C語言的case只能匹配整型或字符型常量表達(dá)式,而Shell腳本的case可以匹配字符串和Wildcard,每個(gè)匹配分支可以有若干條命令,末尾必須以;;結(jié)束,執(zhí)行時(shí)找到第一個(gè)匹配的分支并執(zhí)行相應(yīng)的命令,然后直接跳到esac之后,不需要像C語言一樣用break跳出。

#! /bin/sh

echo "Is it morning? Please answer yes or no."
read YES_OR_NO
case "$YES_OR_NO" in
yes|y|Yes|YES)
echo "Good Morning!";;
[nN]*)
echo "Good Afternoon!";;
*)
echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
exit 1;;
esac
exit 0

使用case語句的例子可以在系統(tǒng)服務(wù)的腳本目錄/etc/init.d中找到。這個(gè)目錄下的腳本大多具有這種形式(以/etc/apache2為例):

case $1 in
start)
...
;;
stop)
...
;;
reload | force-reload)
...
;;
restart)
...
*)
log_success_msg "Usage: /etc/init.d/apache2 {start|stop|restart|reload|force-reload|start-htcacheclean|stop-htcacheclean}"
exit 1
;;
esac

啟動(dòng)apache2服務(wù)的命令是

$ sudo /etc/init.d/apache2 start

$1是一個(gè)特殊變量,在執(zhí)行腳本時(shí)自動(dòng)取值為第一個(gè)命令行參數(shù),也就是start,所以進(jìn)入start)分支執(zhí)行相關(guān)的命令。同理,命令行參數(shù)指定為stop、reloadrestart可以進(jìn)入其它分支執(zhí)行停止服務(wù)、重新加載配置文件或重新啟動(dòng)服務(wù)的相關(guān)命令。

5.4. for/do/done

Shell腳本的for循環(huán)結(jié)構(gòu)和C語言很不一樣,它類似于某些編程語言的foreach循環(huán)。例如:

#! /bin/sh

for FRUIT in apple banana pear; do
echo "I like $FRUIT"
done

FRUIT是一個(gè)循環(huán)變量,第一次循環(huán)$FRUIT的取值是apple,第二次取值是banana,第三次取值是pear。再比如,要將當(dāng)前目錄下的chap0、chap1chap2等文件名改為chap0~、chap1~chap2~等(按慣例,末尾有~字符的文件名表示臨時(shí)文件),這個(gè)命令可以這樣寫:

$ for FILENAME in chap?; do mv $FILENAME $FILENAME~; done

也可以這樣寫:

$ for FILENAME in `ls chap?`; do mv $FILENAME $FILENAME~; done

5.5. while/do/done

while的用法和C語言類似。比如一個(gè)驗(yàn)證密碼的腳本:

#! /bin/sh

echo "Enter password:"
read TRY
while [ "$TRY" != "secret" ]; do
echo "Sorry, try again"
read TRY
done

下面的例子通過算術(shù)運(yùn)算控制循環(huán)的次數(shù):

#! /bin/sh

COUNTER=1
while [ "$COUNTER" -lt 10 ]; do
echo "Here we go again"
COUNTER=$(($COUNTER+1))
done

Shell還有until循環(huán),類似C語言的do...while循環(huán)。本章從略。

5.6. 位置參數(shù)和特殊變量

有很多特殊變量是被Shell自動(dòng)賦值的,我們已經(jīng)遇到了$?$1,現(xiàn)在總結(jié)一下:

表 31.4. 常用的位置參數(shù)和特殊變量

$0 相當(dāng)于C語言main函 數(shù)的argv[0]
$1、$2... 這些稱 為位置參數(shù)(Positional Parameter),相當(dāng)于C 語言main函數(shù)的argv[1]、argv[2]...
$# 相當(dāng)于C語言main函 數(shù)的argc - 1,注意這里的#后 面不表示注釋
$@ 表示參數(shù)列表"$1" "$2" ...,例如可以用在for循 環(huán)中的in后面。
$? 上一條命令的Exit Status
$$ 當(dāng)前Shell的進(jìn)程號(hào)

位置參數(shù)可以用shift命令左移。比如shift 3表示原來的$4現(xiàn)在變成$1,原來的$5現(xiàn)在變成$2等等,原來的$1$2、$3丟棄,$0不移動(dòng)。不帶參數(shù)的shift命令相當(dāng)于shift 1。例如:

#! /bin/sh

echo "The program $0 is now running"
echo "The first parameter is $1"
echo "The second parameter is $2"
echo "The parameter list is $@"
shift
echo "The first parameter is $1"
echo "The second parameter is $2"
echo "The parameter list is $@"

5.7. 函數(shù)

和C語言類似,Shell中也有函數(shù)的概念,但是函數(shù)定義中沒有返回值也沒有參數(shù)列表。例如:

#! /bin/sh

foo(){ echo "Function foo is called";}
echo "-=start=-"
foo
echo "-=end=-"

注意函數(shù)體的左花括號(hào){和后面的命令之間必須有空格或換行,如果將最后一條命令和右花括號(hào)}寫在同一行,命令末尾必須有;號(hào)。

在定義foo()函數(shù)時(shí)并不執(zhí)行函數(shù)體中的命令,就像定義變量一樣,只是給foo這個(gè)名字一個(gè)定義,到后面調(diào)用foo函數(shù)的時(shí)候(注意Shell中的函數(shù)調(diào)用不寫括號(hào))才執(zhí)行函數(shù)體中的命令。Shell腳本中的函數(shù)必須先定義后調(diào)用,一般把函數(shù)定義都寫在腳本的前面,把函數(shù)調(diào)用和其它命令寫在腳本的最后(類似C語言中的main函數(shù),這才是整個(gè)腳本實(shí)際開始執(zhí)行命令的地方)。

Shell函數(shù)沒有參數(shù)列表并不表示不能傳參數(shù),事實(shí)上,函數(shù)就像是迷你腳本,調(diào)用函數(shù)時(shí)可以傳任意個(gè)參數(shù),在函數(shù)內(nèi)同樣是用$0、$1、$2等變量來提取參數(shù),函數(shù)中的位置參數(shù)相當(dāng)于函數(shù)的局部變量,改變這些變量并不會(huì)影響函數(shù)外面的$0$1、$2等變量。函數(shù)中可以用return命令返回,如果return后面跟一個(gè)數(shù)字則表示函數(shù)的Exit Status。

下面這個(gè)腳本可以一次創(chuàng)建多個(gè)目錄,各目錄名通過命令行參數(shù)傳入,腳本逐個(gè)測(cè)試各目錄是否存在,如果目錄不存在,首先打印信息然后試著創(chuàng)建該目錄。

#! /bin/sh

is_directory()
{
DIR_NAME=$1
if [ ! -d $DIR_NAME ]; then
return 1
else
return 0
fi
}

for DIR in "$@"; do
if is_directory "$DIR"
then :
else
echo "$DIR doesn't exist. Creating it now..."
mkdir $DIR > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Cannot create directory $DIR"
exit 1
fi
fi
done

注意is_directory()返回0表示真返回1表示假。


6. Shell腳本的調(diào)試方法

Shell提供了一些用于調(diào)試腳本的選項(xiàng),如下所示:

-n

讀一遍腳本中的命令但不執(zhí)行,用于檢查腳本中的語法錯(cuò)誤

-v

一邊執(zhí)行腳本,一邊將執(zhí)行過的腳本命令打印到標(biāo)準(zhǔn)錯(cuò)誤輸出

-x

提供跟蹤執(zhí)行信息,將執(zhí)行的每一條命令和結(jié)果依次打印出來

使用這些選項(xiàng)有三種方法,一是在命令行提供參數(shù)

$ sh -x ./script.sh

二是在腳本開頭提供參數(shù)

#! /bin/sh -x

第三種方法是在腳本中用set命令啟用或禁用參數(shù)

#! /bin/shif [ -z "$1" ]; thenset -xecho "ERROR: Insufficient Args."exit 1set +xfi

set -xset+x分別表示啟用和禁用-x參數(shù),這樣可以只對(duì)腳本中的某一段進(jìn)行跟蹤調(diào)試。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Shell編程基礎(chǔ)
shell中的點(diǎn)命令與source命令的區(qū)別
求linux 下source、sh、bash、./執(zhí)行腳本的區(qū)別?
Linux Shell編程入門
shell編程規(guī)范及變量
Shell 腳本面試問題大全
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服