主頁 > 知識庫 > shell腳本學(xué)習(xí)指南[三](Arnold Robbins & Nelson H.F. Beebe著)

shell腳本學(xué)習(xí)指南[三](Arnold Robbins & Nelson H.F. Beebe著)

熱門標(biāo)簽:江蘇云電銷機(jī)器人公司 華鋒e路航港口地圖標(biāo)注 打電話機(jī)器人接我是他的秘書 百度地圖標(biāo)注錯(cuò)了有責(zé)任嗎 地圖標(biāo)注員都是年輕人 河南信譽(yù)好的不封卡電話外呼系統(tǒng) 客服外呼系統(tǒng)怎么樣 如果做線上地圖標(biāo)注 揭陽智能電話機(jī)器人推薦

今天木有冷笑話,只有一個(gè)噩耗。噩耗是:今天木有冷笑話?。?!
不要總想著冷笑話嘛,有點(diǎn)追求,聽毛主席的話:好好學(xué)習(xí),天天向上!

第七章輸入輸出、文件與命令執(zhí)行

學(xué)C的應(yīng)該了解標(biāo)準(zhǔn)輸入輸出和錯(cuò)誤輸出吧?感覺總打很多字進(jìn)度太慢,所以一直在省略類似C的東西,也方便以后看這篇文章的人能夠快速學(xué)完shell腳本(或者是快速看完這本書)。

讀取行read命令是重要方式之一,它可以自標(biāo)準(zhǔn)輸入讀取行后,通過shell字段切割的功能(使用$IFS)進(jìn)行切分,第一部分給第一個(gè)變量,第二部分給第二個(gè),類推。如果切割單詞多余變量,則剩下所有的給最后一個(gè)變量。如果輸入行以反斜杠結(jié)尾,則read會丟棄反斜杠與換行符繼續(xù)讀取下行數(shù)據(jù)。它有一個(gè)選項(xiàng) -r,它將忽視最后反斜杠當(dāng)讀入數(shù)據(jù)。使用read可能的一個(gè)錯(cuò)誤是通過循環(huán)讓read讀取一個(gè)文件如:
while IFS=: read user pass uid gid fullname homedir shell /etx/passwd ... 這個(gè)循環(huán)將一直下去并且每次read只讀passwd的第一行。因?yàn)槊看窝h(huán)都重新打開了passwd文件讀取內(nèi)容。解決辦法是: cat /etc/passwd | while IFS=: read ....... 這樣通過管道解決讀取文件問題。 這里有一個(gè)概念,文件描述符,一般這個(gè)文件描述符是由0-9這幾個(gè)數(shù)字來描述的,所以傳統(tǒng)上shell也就允許你最多打開十個(gè)文件。比如make 1> results 2> ERRS 。命令make的標(biāo)準(zhǔn)輸出(文件描述符1)傳給results,并將錯(cuò)誤輸出(文件描述符2)傳給ERRS。設(shè)置完文件描述符后,如何引用呢?像這樣:make > results 2>1 。
1> results這里的1其實(shí)沒必要,供輸出重定向的默認(rèn)文件描述符是標(biāo)準(zhǔn)輸出,也就是文件描述符1,重定向 > results讓文件描述符1作為文件results,接下來重定向2>1有兩部分,2>重定向文件描述2,也就是標(biāo)準(zhǔn)錯(cuò)誤輸出。而1就是剛才我們的疑問,用來引用我們定義的文件描述符。特別注意:2>1這樣的四個(gè)字符一定要連著寫。

再介紹一個(gè)用來改變shell本身I/O設(shè)置的exec命令。如果只有I/O重定向而沒有任何參數(shù)時(shí),exec會改變shell的文件描述符:

復(fù)制代碼 代碼如下:

exec 2> /tmp/$0.log #重定向shell本身的標(biāo)準(zhǔn)錯(cuò)誤輸出
exec 3 /some/file #打開新文件描述符3
...
read name rank serno 3 #從該文件讀取 exec 3>- #關(guān)閉文件描述符3

上例展示了如何關(guān)閉文件描述符。
exec還有一個(gè)功能就是在當(dāng)前shell下執(zhí)行指定的程序。

書中對printf做了完整的介紹,這里就不再介紹了,就是C里邊的那些東西。

shell中有兩種與文件名相關(guān)的展開:第一個(gè)是波浪號展開(~ tilde expansion),另一個(gè)叫法較多如通配符展開式(wildcard expansion)、全局展開(globbing)或路徑展開(pathname expansion)。

如果命令行字符第一個(gè)字符為波浪號或者變量指定的值里任何未被引號括起來的冒號之后的第一個(gè)字符為波浪號時(shí),shell便會執(zhí)行波浪號展開。波浪號展開的目的,是要將用戶根目錄的符號型表示方式,改為實(shí)際的目錄路徑。

shell環(huán)境下的通配符展開,有幾個(gè)基本的通配符:? , * , [set] [!set] ,前倆略過,第三個(gè)是匹配出現(xiàn)中括號集合中的字符,第四個(gè)取反義。比如可以查找 *.html 就知道處所有類似的文件。另外有一點(diǎn)注意的,在linux下文件名里的.號沒有任何特殊意義,匹配所有文件時(shí)只需用一個(gè)*即可,不用像windows下那樣*.*。
習(xí)慣上,當(dāng)執(zhí)行通配符展開時(shí),shell會忽略文件名開頭為一個(gè)點(diǎn)號的文件。像這樣的點(diǎn)號文件(dot files)通常用作程序配置文件或啟動文件。

命令替換,書上寫的概念很繞口,其實(shí)就是一個(gè)命令的用法或者寫法,例如:
echo outer `echo inner1 \`echo inner2 \` `
這樣輸出結(jié)果就是 outer inner1 inner2 類似命令嵌套,從最內(nèi)層開始執(zhí)行。注意是反單引號,鍵盤1左邊與波浪號同鍵那個(gè)。但是這樣嵌套多了之后會非常難以閱讀,就出現(xiàn)了新的語法:
$ echo outer $(echo inner1 $(echo inner2) inner1) outer
這樣輸出結(jié)果就是outer inner1 inner2 inner1 outer。這樣清晰多了。

書中教了一個(gè)expr命令,從提到這個(gè)命令,到接下來的兩段都在說這個(gè)命令不好用,并且可以是由$(( )) ,test替代。但是可以了解一下,作用就是計(jì)算之后跟著的一個(gè)表達(dá)式比如:expr 1 + 1 。這里注意加號兩邊的空格,是必要的,書里貌似沒說,郁悶半天才發(fā)現(xiàn)必須添加空格。。- -!

這里又提了引用,就是說用來防止shell將某些東西解釋成你不想要的意義,比如你就是有就想要*,而不是需要一個(gè)通配符,這時(shí)候你需要轉(zhuǎn)義(\) ,或者是單引號引起來(單引號引起來的內(nèi)容轉(zhuǎn)義符號也無效),或是雙引號?;煊玫臅r(shí)候請小心。

書中詳細(xì)說了一下命令的執(zhí)行順與,感覺很有必要細(xì)看一下,全都摘錄一下。
shell從標(biāo)準(zhǔn)輸入或腳本中讀取的每一行成為管道;它包含了一個(gè)或多個(gè)命令,這些命令被零或多個(gè)管道字符隔開。事實(shí)上還有很多特殊符號可用來分隔單個(gè)的命令:分號;、管道|、、邏輯AND、還有邏輯OR||。對于每一個(gè)讀取的管道,shell都會將命令分割,為管道設(shè)置I/O,并且對每一個(gè)命令依次執(zhí)行下面操作:
1、將命令分割成token,是以固定的一組meta字符分隔,有空格、制表符、換行字符、;、(、)、、>、|、和。token的種類包括單詞、關(guān)鍵字、輸出入重定向器,以及分號。這是微妙的,但是變量、命令還有算符替換,都可以在shell執(zhí)行token認(rèn)定的時(shí)候被執(zhí)行。這就是為什么先前所舉例子vi ~$user/.profile中波浪符號可以展開像預(yù)期的那樣工作。
2、檢查每個(gè)命令的第一個(gè)token,看看是否它是不帶有引號或反斜杠的關(guān)鍵字。如果它是一個(gè)開放的關(guān)鍵字(if 或者 { (之類的),則這個(gè)命令其實(shí)是一個(gè)復(fù)合命令。shell為復(fù)合命令進(jìn)行內(nèi)部的設(shè)置,讀取下一條命令,并再次啟動進(jìn)程。如果關(guān)鍵字非復(fù)合命令的開始符號(例如,它是控制結(jié)構(gòu)的中間部分,像then、else或do,或是結(jié)尾部分,例如fi,done或邏輯運(yùn)算符),則shell會發(fā)出語法錯(cuò)誤的信號。
3、將每個(gè)命令的第一個(gè)單詞與別名列表對照檢查。如果匹配,它便代替別名的定義,并回到步驟1;否則,進(jìn)行步驟4(別名是給交互式shell使用)。回到步驟1,允許讓關(guān)鍵字的別名被定義:例如alias aslongas=while or alias procedure=function。注意,shell不會執(zhí)行遞歸的別名展開,反而當(dāng)別名展開為相同的命令時(shí)它會知道,并停止?jié)撛诘倪f歸操作??梢酝ㄟ^引用要保護(hù)的單詞的任何部分而禁止別名展開。
4、如果波浪號字符出現(xiàn)在單詞的開頭處,則將 波浪號替換成用戶的跟目錄$HOME,將~user替換成user的根目錄。
波浪號替換會發(fā)生在下面的位置:
* 在命令行里,作為單詞的第一個(gè)未引用字符
* 在變量賦值中的=之后以及變量賦值中的任何:之后
* 形式${ varibale op word } 的變量替換里的word部分
5、將任何開頭為$符號的表達(dá)式,執(zhí)行參數(shù)(變量)替換。
6、將任何形式為$(string)或 `string`的表達(dá)式,執(zhí)行命令替換。
7、執(zhí)行形式$((string))的算術(shù)表達(dá)式。
8、從參數(shù)、命令與算術(shù)替換中取出結(jié)果行的部分,再一次將它們切分為單詞。這次它使用$IFS里的字符作為定界符,而不是使用步驟1的那組meta字符。通常,在IFS里連續(xù)多個(gè)重復(fù)的輸入字符是作為單一定界符,這是你所期待的。這只有對空白字符而言是真的。對非空白字符,則不是這樣的。舉例說,當(dāng)讀取以冒號分隔字段的/etc/passwd文件時(shí),兩個(gè)連續(xù)冒號所界定的是一個(gè)空子段。
9、對于*、?以及一對[...]的任何出現(xiàn)次數(shù),都執(zhí)行文件名生成的操作,也就是通配符展開。
10、使用第一個(gè)單詞作為一個(gè)命令,遵循查找次序,也就是,先作為個(gè)特殊的內(nèi)建命令,接著是作為函數(shù),然后作為一般的內(nèi)建命令,以及最后作為查找$PATH找到的第一個(gè)文件。
11、在完成I/O重定向與其他同類型事項(xiàng)之后,執(zhí)行命令。

shell程序碰到一句命令,都會執(zhí)行上邊的一次流程。比如:

復(fù)制代碼 代碼如下:

$ mkdir /tmp/x
$ cd /tmp/x
$ touch f1 f2
$ f=f y="a b"
$ echo ~+/${f}[12] $y $(echo cmd subst) $((3+2)) > out

命令一開始會根據(jù)shell語法分割token,最重要一點(diǎn)是I/O重定向 > out 在這里是被識別的,并存儲供稍后使用。最后這句echo被分為5個(gè)token,分別是: echo ,~+/${f}[12] , $y , $(echo cmd subst) ,$((3 + 2))這5個(gè)部分。
然后檢查第一個(gè)單詞echo是否為關(guān)鍵字,例如if或for,這里不是所以命令不變繼續(xù)處理。
檢查第一個(gè)單詞依然是echo 是否為別名,這里不是,繼續(xù)執(zhí)行。
掃描所有單詞是否需要波浪號展開,本例中,~+為ksh93與bash的擴(kuò)展,等同于$PWD,也就是當(dāng)前的目錄。token 2將被修改變成如下:
echo /tmp/x/${f}[12] $y $(echo cmd subst) $((3 + 2))
下一步變量展開:token2與token3被修改變成:
echo /tmp/x/f[12] a b $(echo cmd subst) $((3 + 2))
再來要處理的是命令替換。注意,這里可遞歸引用列表里的所有步驟!在次例中,因?yàn)槲覀冊噲D讓所有的東西容易理解,因此命令替換修改了token4,結(jié)果如下:
echo /tmp/x/f[12] a b cmd subst $((3 + 2))
現(xiàn)在執(zhí)行算術(shù)替換,結(jié)果如下:
echo /tmp/x/f[12] a b cmd subst 5
前面所有的展開產(chǎn)生的結(jié)果,都將再一次被掃描,看看是否有#IFS字符,如果有則它們是作為分隔符,產(chǎn)生額外的單詞。
最后的替換階段是通配符展開變化如下:
echo /tmp/x/f1 /tmp/x/f2 a b cmd subst 5
這時(shí),shell已經(jīng)準(zhǔn)備好要執(zhí)行最后的命令了。它會去尋找echo。正好ksh93與bash里的echo都已內(nèi)建到shell中。
shell實(shí)際執(zhí)行命令。首先執(zhí)行>out的重定向,再調(diào)用內(nèi)部的echo版本。這一系列完成了這句語句的執(zhí)行。

eval語句是再告知shell取出eval的參數(shù),并再執(zhí)行它們一次,是他們經(jīng)過整個(gè)命令行的處理步驟。看一個(gè)例子:

復(fù)制代碼 代碼如下:

listpage="ls | more"
$listpage

運(yùn)行之后你會發(fā)現(xiàn)shell把|與more看作ls的參數(shù),而不是直接產(chǎn)生一頁頁的文件列表。這是由于在shell執(zhí)行變量時(shí),管道字符出現(xiàn)在步驟5,也就是在它確實(shí)尋找管道字符之后(在步驟1).變量的展開一直要到步驟8才進(jìn)行解析。結(jié)果,shell把 | 與more看作ls的參數(shù),使得ls會試圖在當(dāng)前目錄下尋找名為|與more的文件。

現(xiàn)在,想想eval $listpage吧,在shell到達(dá)最后一步時(shí),會執(zhí)行帶有l(wèi)s、|與more參數(shù)的eval,這會讓shell回到步驟1,具有一行包括了這些參數(shù)的命令。在步驟1發(fā)現(xiàn)|后,將該行分割為兩個(gè)命令:ls 和more。每個(gè)要被處理的命令都以一般方式執(zhí)行,最后的結(jié)果是在當(dāng)前目錄下分頁的文件列表。

還有兩個(gè)其他的結(jié)構(gòu),有時(shí)也很有用,subShell與代碼塊。
subShell是一群被括在圓括號里的命令,這些命令會在另外的進(jìn)程中執(zhí)行。當(dāng)你需要讓一小組的命令在不同的目錄下執(zhí)行時(shí),這些命令會在另外的進(jìn)程中執(zhí)行。如:
tar -cf - . | (cd /newdir; tar -xpf - )
左邊tar產(chǎn)生當(dāng)前目錄打包文件,將它傳送給標(biāo)準(zhǔn)輸出。右邊的cd命令會先切換到新目錄,也就是讓打包文件在此目錄下解開。然后,右邊的tar將從打包文件里解開文件,請注意,執(zhí)行此管道的shell并未更改它的目錄。

代碼塊概念上與subShell雷同,只不過它不會建立新進(jìn)程。代碼塊用花括號括起。且會對主腳本造成影響(比如當(dāng)前目錄)。一般花括號被視為關(guān)鍵字,即它們只有出現(xiàn)在命令的第一個(gè)符號時(shí)被識別。實(shí)際上,這表示你必須將結(jié)束花括號放置在換行符或分號之后。

shell有很多命令,之前說過,特殊內(nèi)建命令與一般內(nèi)建命令的差別在于shell查找要執(zhí)行的命令時(shí),會先查找特殊內(nèi)建命令再找shell函數(shù),接下來才是一般內(nèi)建命令。最后是$PATH路徑內(nèi)的外部命令。這種查找順序讓定義shell函數(shù)以擴(kuò)展或覆蓋一般shell內(nèi)建命令成為可能。舉例說你希望shell的提示號能包含當(dāng)前目錄路徑的最后一個(gè)組成部分。最簡單的實(shí)現(xiàn)方式,就是在每次改變目錄時(shí),都讓shell改變PS1.你可以寫一個(gè)自己專用的函數(shù)如下:

復(fù)制代碼 代碼如下:

# chdir ---改變目錄時(shí)更新PS1的個(gè)人函數(shù)
chdir () {
cd "$@" #實(shí)際更改目錄
x=$(pwd) #取得當(dāng)前目錄的名稱
PS1="${x##*/}\$ " #截?cái)嗲懊娴慕M成部分后,指定給PS1
}

這么做有個(gè)問題,你必須在shell下輸入chdir而不是cd,這樣你可以自己寫一個(gè)名為cd的函數(shù),然后shell會先找到你的cd函數(shù),而不是一般內(nèi)建函數(shù)cd。但是這樣又會有問題,shell函數(shù)如何真正訪問cd命令,這里函數(shù)內(nèi)cd會再此調(diào)用你寫的cd函數(shù)導(dǎo)致遞歸出現(xiàn)。這時(shí)候我們需要轉(zhuǎn)義策略,使用內(nèi)建命令command來告訴shell要避開函數(shù)的查找直接訪問真正的命令。

#cd --改變目錄時(shí)更新PS1的私人版
cd(){
command cd "$@"
x=$(pwd)
PS1="${x##*/}\$ "
}

第八章產(chǎn)生腳本


詳細(xì)講了一下set命令。。。

這一章詳解了兩個(gè)好用腳本的實(shí)現(xiàn)過程,這兩個(gè)腳本詳解內(nèi)容揉雜在注釋里給出。

腳本一:功能是在給出的路徑下查找目標(biāo)路徑

復(fù)制代碼 代碼如下:

#! /bin/sh -
#
# 標(biāo)準(zhǔn)輸出所產(chǎn)生的結(jié)果,通常是查找路徑下找到的每一個(gè)文件之第一個(gè)實(shí)體的完成路徑,
# 或是“filename: not found ”的標(biāo)準(zhǔn)錯(cuò)誤輸出。
#
# 如果所有文件都找到,則退出碼為0,
# 否則,即為找不到的文件個(gè)數(shù)(非0)
# shell的退出碼限制為125

# 語法:
#      pathfind [--all] [--?] [--help] [--version] envvar pattern(s)
#
# 使用--all選項(xiàng)時(shí),在路徑下的每一個(gè)目錄都會被查找,
# 而非停在第一個(gè)找到者。
# 所有腳本的頭部說明腳本功能是必不可少的,對人閱讀很有用。

#在網(wǎng)絡(luò)的環(huán)境下,安全性一直是必須慎重考慮的問題。其中有一種攻擊shell腳本  #的方式,是利用輸入字段分隔字符:IFS,它會影響shell接下來對輸入數(shù)據(jù)解釋的
#方式。為避免此類的攻擊,部分shell僅在腳本執(zhí)行前,將IFS重設(shè)為標(biāo)準(zhǔn)值;其他
#則導(dǎo)入該變量的一個(gè)外部設(shè)置。很難在屏幕上看出來,單引號內(nèi)包含一個(gè)換行一
#個(gè)空格和一個(gè)制表符,這是IFS的默認(rèn)值。也可以使用轉(zhuǎn)義\040\t\n,但bourne
#shell不支持這一的轉(zhuǎn)義。重新定義IFS時(shí)有一點(diǎn)要特別留意,當(dāng)"$*"展開以回復(fù)命
#令行時(shí),IFS值的第一個(gè)字符,會被當(dāng)成字段分隔符。這里不使用$*,不受影響
IFS='
  '
#另一種常見的安全性攻擊,則是欺騙軟件,它執(zhí)行非我們所預(yù)期的命令。為了阻斷
#這種攻擊,我們希望調(diào)用的程序是可信任的版本,而非潛伏在用戶提供的查找路徑
#下的欺騙程序,因此我們將PATH設(shè)最小值,存儲初始值供以后使用。exprot語句
#是這里的關(guān)鍵么,它可以確保所有子進(jìn)程繼承我們的安全查找路徑。
OLDPATH="$PATH"

PATH=/bin:/usr/bin
export PATH
#錯(cuò)誤輸出函數(shù)
error(){
 echo "$@" 1>2
 usage_and_exit 1
}
#簡短的一個(gè)信息提示函數(shù),$PROGRAM稍后會賦值為命令名
usage(){
 echo "Usage: $PROGRAM [--all] [--?] [--help] [--version] envvar pattern(s)"
}
#提供信息和狀態(tài)碼退出
usage_and_exit(){
 usage
 exit $1
}
#提供用戶版本號
version(){
 echo "$PROGRAM version $VERSION"
}
#給出警告信息并在狀態(tài)碼上加1,記錄警告次數(shù)
warning(){
 echo "$@" 1>2
 EXITCODE=`expr $EXITCODE + 1`
}
#按大寫全局,小寫局部命名規(guī)則,初始化需要用的變量
all=no
envvar=
EXITCODE=0
#basename會截去參數(shù)最后一個(gè)斜杠之前字串,返回剩下的部分
PROGRAM=`basename $0`
VERSION=1.0
#接下來這塊就是linux經(jīng)典的命令行參數(shù)解析部分了,不多解釋,需要注意的是?號
#是通配符,所以篩選選項(xiàng)的時(shí)候防止展開加上單引號。
while test $# -gt 0
do
 case $1 in
 --all | --al | --a | -all | -al | -a )
  all=yes

 --help | --hel | --he | --h | '--?' | -help | -hel | -he | -h | '-?' )
  usage_and_exit 0

 --version | --versio | --versi | --vers | --ver | --ve | --v | \
 -version | -versio | -versi | -vers | -ver | -ve | -v )
  version
  exit 0

 -* )
  error "Unrecognized option: $1"

 * )
  break

#這里我小糾結(jié)了一下esac命令,沒搞清楚在這干嘛的,也沒這個(gè)命令說明,仔細(xì)看
#看才發(fā)現(xiàn)是case逆序?qū)懛?,是case的結(jié)束標(biāo)志,就像if結(jié)束標(biāo)志fi一樣
 esac
 shift
done
#下面我們要處理除選項(xiàng)外的參數(shù)了,我們可以用"$@"來取得,但是避免將他們存
#儲在變量內(nèi)如files="$@",因?yàn)槲募腥绻锌崭駥o法正確被處理。
envvar="$1"
test $# -gt 0 shift
#因?yàn)橛锌赡苡脩籼峁┑沫h(huán)境變量是PATH,為安全性考慮會重設(shè),這是我們檢測該變
#量,并適當(dāng)更新envvar,開頭的x是為了避免開展當(dāng)成test的選項(xiàng)。
test "x$envvar" = "xPATH" envvar=OLDPATH

#下邊這句雖然很段,但是最棘手部分:使用shell的eval語句。我們envvar里已經(jīng)擁
#有了環(huán)境變量的名稱,可以"$envvar"取得,但我們現(xiàn)在要的是它的展開,我們也
#想要冒號分隔符轉(zhuǎn)換成一般空白分隔符。如果MYPATH為用戶所提供的名稱,我們
#便會構(gòu)建參數(shù)字符串'${'"$envvar"'}',也是shell展開為'${MYPATH}'的等同物。兩邊
#的單引號是為了避免它更進(jìn)一步展開,該字串傳給eval,它會將其視為兩個(gè)參數(shù):
#echo與${MYPATH}。eval在環(huán)境下尋找MYPATH,假設(shè)找到就執(zhí)行展開命令,并輸
#出,通過管道傳給tr命令將冒號轉(zhuǎn)換為空格,最后將轉(zhuǎn)化值給dirpath,錯(cuò)誤信心隱藏
#輸給/dev/null
dirpath=`eval echo '${'"$envvar"'}' 2>/dev/null | tr : ' ' `

#為錯(cuò)誤情況進(jìn)行健全檢測
if test -z "$envvar"
then
 error Environment variable missing or empty
elif test "x$dirpath" = "x$envvar"
then
 error "Broken sh on this platform: cannot expand $envvar"
elif test -z "$dirpath"
then
 error Empty directory search path
elif test $# -eq 0
then
 exit 0
fi

#接下來三重循環(huán),外層處理參數(shù)文件或模式,中層循環(huán)處理查找路徑下的目錄,內(nèi)
#層循環(huán)匹配單一目錄下的文件。
for pattern in "$@"
do
 result=
 for dir in $dirpath
 do
  for file in $dir/$pattern
  do
   if test -f "$file"
   then
    result="$file"
    echo $result
    test "$all" = "no" break 2
   fi
  done
 done
 test -z "$result" warning "$pattern: not found"
done

#限制退出狀態(tài)是一般linux實(shí)現(xiàn)上的限制
test $EXITCODE -gt 125 EXITCODE=125
exit $EXITCODE

這里作者給留了課后作業(yè):增添一個(gè)功能,不單單只能匹配文件,也能匹配其他東西比如:符號性連接文件,可讀取文件或者可執(zhí)行文件之類的,需要test -x選項(xiàng)來進(jìn)行匹配,本人完成的如下:

復(fù)制代碼 代碼如下:

#變量初始化的地方增添一個(gè)test選項(xiàng)變量,默認(rèn)為f
testopt=f

#選項(xiàng)解析的位置增添test選項(xiàng)并檢測合法性
        --test | --tes | --te | --t | -test | -tes | -te | -t )
                echo $2
                if echo $2 | grep -e "^[bcdefgGhkLOPrSstuwx]\{1\}$"
                then
                        testopt=$2
                        shift
                else
                        error "Unrecognized --test option: $2"
                fi

 
#最后循環(huán)test匹配位置改為:
  if test -"$testopt" "$file"

歐了,課后作業(yè)完成,安全性還沒經(jīng)驗(yàn),有老師批改作業(yè)木有?

下邊給了第二個(gè)腳本,是軟件構(gòu)建自動化,代碼灰長長。。。這個(gè)打代碼都碼的頭疼了,跳過吧,有興趣童鞋自己搞定。我是不求甚解的先趕進(jìn)度了,回來再搞。

復(fù)制代碼 代碼如下:

#! /bin/sh -
# 在一臺或多臺構(gòu)建主機(jī)上,并行構(gòu)建一個(gè)或多個(gè)包
#
# 語法:
# build-all [ --? ]
#    [ --all "..." ]
#    [ --check "..." ]
#    [ --configure "..." ]
#    [ --environment "..." ]
#    [ --help ]
#    [ --logdirectory dir ]
#    [ --on "[user@]host[:dir][,envfile] ..." ]
#    [ --source "dir..." ]
#    [ --userhosts "file(s)" ]
#    [ --version ]
#    package(s)
#
# 可選用的初始化文件:
# $HOME/.build/directories list of source directories
# $HOME/.build/userhosts  list of [user@]host[:dir][,envfile]

IFS='
  '

PATH=/usr/local/bin:/bin:/usr/bin
export PATH

UMASK=002
umask $UMASK

build_one(){
#語法:
# build_one [user@]host[:build-directory][,envfile]
 arg="`eval echo $1`"

 userhost="`echo $arg | sed -e 's/:.*$//'`"

 user="`echo $userhost | sed -e s'/@.*$//'`"
 test "$user" = "$userhost" user=$USER

 host="`echo $userhost | sed -e s'/^[^@]@//'`"

 envfile="`echo $arg | sed -e 's/^[^,]*,//'`"
 test "$envfile" = "$arg" envfile=/dev/null

 builddir="`echo $arg | sed -e s'/^[^,]*,//'`"
 test "$builddir" = "$arg" builddir=/tmp

 parbase=`basename $PARFILE`
 #NB:如果這些模式被更換過,則更新find_package()
 package="`echo $parbase | \
  sed -e 's/[.]jar$//' \
           -e 's/[.]tar[.]bz2$//' \
           -e 's/[.]tar[.]gz$//' \
           -e 's/[.]tar[.]Z$//' \
           -e 's/[.]tar$//' \
           -e 's/[.]taz$//' \
           -e 's/[.]zip$//'`"
 #如果我們在遠(yuǎn)程主機(jī)上看不到包文件,則復(fù)制過去
 echo $SSH $SSHFLAGS $userhost "test -f $PARFILE"
 if $SSH $SSHFLAGS $userhost "test -f $PARFILE"
 then
  parbaselocal=$PARFLE
 else
  parbaselocal=$parbase
  echo $SCP $PARFILE $userhost:$builddir
  $SCP $PARFILE $userhost:$builddir
 fi
 #在遠(yuǎn)程主機(jī)上解開存檔文件、構(gòu)建,以及后臺執(zhí)行方式檢查它
 sleep 1  #為了保證唯一的日志文件名
 now="`date $DATEFLAGS`"
 logfile="$package.$host.$now.log"
 nice $SSH $SSHFLAGS $userhost "
  echo '==================================================' ;
  test -f $BUILDBEGIN . $BUILDBEGIN || \
   test -f $BUILDBEGIN source $BUILDBEGIN || \
    true ;
  echo 'Package:   $package' ;
  echo 'Archive:   $PARFILE' ;
  echo 'Date:   $now' ;
  echo 'Local user:  $USER' ;
  echo 'Local host:  `hostname`' ;
  echo 'Local log directory: $LOGDIR' ;
  echo 'Local log file:  $logfile' ;
  echo 'Remote user:  $user' ;
  echo 'Remote host:  $host' ;
  echo 'Remote directory:  $builddir' ;
  printf 'Remote date:  ' ;
  date $DATEFLAGS ;
  printf 'Remote uname:  ' ;
  uname -a || true ;
  printf 'Remote gcc version: ' ;
  gcc --version | head -n 1 || echo ;
  printf 'Remote g++ version: ' ;
  g++ --version | head -n 1 || echo ;
  echo 'Configure environment: `$STRIPCOMMENTS $envfile | \
   $JOINLINES`' ;
  echo 'Extra environment:  $EXTRAENVIRONMENT' ;
  echo 'Configure directory: $CONFIGUREDIR' ;
  echo 'Configure flags:  $CONFIGUREFLAGS' ;
  echo 'Make all targets:  $ALLTARGETS' ;
  echo 'Make check targets: $CHECKTARGETS' ;
  echo 'Disk free report for $builddir/$package:' ;
  df $builddir | $INDENT ;
  echo 'Environment:' ;
  env | env LC_ALL=C sort | $INDENT ;
  echo '==============================================' ;
  umask $UMASK ;
  cd $builddir || exit 1 ;
  /bin/rm -rf $builddir/$package ;
  $PAR $parbaselocal ;
  test "$parbase" = "$parbaselocal" /bin/rm -f $parbase ;
  cd $package/$CONFIGUREDIR || exit 1 ;
  test -f configure \
   chmod a+x configure \
    env `$STRIPCOMMENTS $envfile | $JOINLINES` \
     $EXTRAENVIRONMENT \
     nice time ./configure $CONFIGUREFLAGS ;
  nice time make $ALLTARGETS nice time make $CHECKTARGETS ;
  echo '===============================================' ;
  echo 'Disk free report for $builddir/$package:' ;
  df $builddir | $INDENT ;
  printf 'Remote date: ' ;
  date $DATEFLAGS ;
  cd ;
  test -f $BUILDEND . $BUILDEND || \
   test -f $BUILDEND source $BUILDEND || \
    true;
  echo '===============================================' ;
 " /dev/null > "$LOGDIR/$logfile" 2>1
}

error(){
 echo "$@" 1>2
 usage_and_exit 1
}

find_file(){
#語法:
# find_file file program-and-args
#如果找到,返回0,如果找不到返回1
 if test -r "$1"
 then
  PAR="$2"
  PARFILE="$1"
  return 0
 else
  return 1
 fi
}

find_package(){
#語法:
# find_package package-x.y.z
 base=`echo "$1" | sed -e 's/[-_][.]*[0-9].*$//'`
 PAR=
 PARFILE=
 for srcdir in $SRCDIRS
 do
  test "$srcdir" = "." srcdir="`pwd`"
  for subdir in "$base" ""
  do
  #如果此列表有改變,則更新build_one()內(nèi)的包設(shè)置
  find_file $srcdir/$subdir/$1.tar.gz "tar xfz" return
  find_file $srcdir/$subdir/$1.tar.Z "tar xfz" return
  find_file $srcdir/$subdir/$1.tar "tar xf" return
  find_file $srcdir/$subdir/$1.tar.bz2 "tar xfj" return
  find_file $srcdir/$subdir/$1.tar.tgz "tar xfz" return
  find_file $srcdir/$subdir/$1.tar.zip "unzip -q" return
  find_file $srcdir/$subdir/$1.jar "jar xf" return
  done
 done
}

set_userhosts(){
#語法:
# set_userhosts file(s)
 for u in "$@"
 do
  if test -r "$u"
  then
   ALTUSERHOSTS="$ALTUSERHOSTS $u"
  elif test -r "$BUILDHOME/$u"
  then
   ALTUSERHOSTS="$ALTUSERHOSTS $BUILDHOME/$u"
  else
   error "File not found: $u"
  fi
 done
}

usage(){
cat EOF
 Usage:
  $PROGRAM [ --? ]
    [ --all "..." ]
    [ --check "..." ]
    [ --configure "..." ]
    [ --environment "..." ]
    [ --help ]
    [ --logdirectory dir ]
    [ --on "[user@]host[:dir][,envfile] ..." ]
    [ --source "dir ..." ]
    [ --userhosts "file(s)" ]
    [ --version ]
    package(s)
EOF
}

usage_and_exit(){
 usage
 exit $1
}

version(){
 echo "$PROGRAM version $VERSION"
}

warning(){
 echo "$@" 1>2
 EXITCODE=`expr $EXITCODE + 1 `
}

ALLTARGETS=
altlogdir=
altsrcdirs=
ALTUSERHOSTS=
BUILDBEGIN=./.build/begin
BUILDEND=./.build/end
BUILDHOME=$HOME/.build
CHECKTARGETS=check
CONFIGUREDIR=.
CONFIGUREFLAGS=
DATEFLAGS="+%Y.%m.%d.%H.%M.%S"
EXITCODE=0
EXTRAENVIRONMENT=
INDENT="awk '{ print \"\t\t\t\" \$0 }'"
JOINLINES="tr '\n' '\040'"
LOGDIR=
PROGRAM=`basename $0`
SCP=scp
SSH=ssh
SSHFLAGS=${SSHFLAGS--x}
STRIPCOMMENTS='sed -e s/#.*$//'
userhosts=
VERSION=1.0

#默認(rèn)的初始化文件
defaultdirectories=$BUILDHOME/directories
defaultuserhosts=$BUILDHOME/userhosts

#要尋找包分發(fā)的位置列表,如果用戶未提供個(gè)人化列表,則使用默認(rèn)列表:
SRCDIRS="`$STRIPCOMMENTS $defaultdirectories 2> /dev/null`"
test -z "$SRCDIRS" \
 SRCDIRS=".
  /usr/local/src
  /usr/local/gnu/src
  $HOME/src
  $HOME/gnu/src
  /tmp
  /usr/tmp
  /var/tmp"
while test $# -gt 0
do
 case $1 in
 --all | --al | --a | -all | -al | -a )
  shift
  ALLTARGETS="$1"

 --cd | -cd )
  shift
  CONFIGUREDIR="$1"

 --check | --chec | --che | --ch | -check | -chec | -che | -ch )
  shift
  CHECKTARGETS="$1"

 --configure | --conf | --co | -configure | -conf | -co )
  shift
  CONFIGUREFLAGS="$1"

 --environment | --environ | -- envir | --e | -environment | \
  -environ | -envir | -e )
  shift
  EXTRAENVIRONMENT="$1"

 --help | --h | '--?' | -help | -h | '-?' )
  usage_and_exit 0

 --logdirectory | --log | --l | -logdirectory | -log | -l )
  shift
  altlogdir="$1"

 --on | --o | -on | -o )
  shift
  userhosts="$userhosts $1"

 --source | --s | -source | -s )
  shift
  altsrcdirs="$altsrcdirs $1"

 --userhosts | --u | -userhosts | -u )
  shift
  set_userhosts $1

 --version | --v | -version | -v )
  version
  exit 0

 -* )
  error "Unrecognized option: $1"

 * )
  break

 esac
 shift
done

#尋找適當(dāng)?shù)泥]件客戶端程序
for MAIL in /bin/mailx /usr/bin/mailx /usr/sbin/mailx /usr/ucb/mailx \
  /bin/mail /usr/bin/mail
do
 test -x $MAIL break
done
test -x $MAIL || error "Cannot find mail client"

#命令行來源目錄優(yōu)先于默認(rèn)值
SRCDIRS="$altsrcdirs $SRCDIRS"

if test -n "$userhosts"
then
 test -n "$ALTUSERHOSTS"
   userhosts="$userhosts `$STRIPCOMMENTS $ALTUSERHOSTS 2> /dev/null`"
else
 test -z "$ALTUSERHOSTS" ALTUSERHOSTS="$defaultuserhosts"
 userhosts="`$STRIPCOMMENTS $ALTUSERHOSTS 2> /dev/null`"
fi

#檢查是否要執(zhí)行某些操作
test -z "$userhosts" usage_and_exit 1

for p in "$@"
do
 find_package "$p"

 if test -z "$PARFILE"
 then
  warning "Cannot find package file $p"
 fi

 LOGDIR="$altlogdir"
 if test -z "$LOGDIR" -o ! -d "$LOGDIR" -o ! -w "$LOGDIR"
 then
  for LOGDIR in "`dirname $PARFILE`/logs/$p" \
  $BUILDHOME/logs/$p /usr/tmp /var/tmp /tmp
  do
   test -d "$LOGDIR" || mkdir -p "LOGDIR" 2> /dev/null
   test -d "$LOGDIR" -a -w "$LOGDIR" break
  done
 fi

 msg="Check build logs for $p in `hostname`:$LOGDIR"
 echo "$msg"
 echo "$msg" | $MAIL -s "$msg" $USER 2> /dev/null

 for u in $userhosts
 do
  build_one $u
 done
done

#將退出狀態(tài)限制為一般unix實(shí)際做法
test $EXITCODE -gt 125 EXITCODE=125

exit $EXITCODE

個(gè)人原創(chuàng),轉(zhuǎn)載請注明:三江小渡

您可能感興趣的文章:
  • shell腳本學(xué)習(xí)指南[二](Arnold Robbins & Nelson H.F. Beebe著)
  • shell腳本學(xué)習(xí)指南[一](Arnold Robbins & Nelson H.F. Beebe著)
  • shell腳本學(xué)習(xí)指南[六](Arnold Robbins & Nelson H.F. Beebe著)
  • shell腳本學(xué)習(xí)指南[五](Arnold Robbins & Nelson H.F. Beebe著)
  • shell腳本學(xué)習(xí)指南[四](Arnold Robbins & Nelson H.F. Beebe著)

標(biāo)簽:婁底 巴彥淖爾 淘寶邀評 馬鞍山 金昌 邵陽 許昌 赤峰

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《shell腳本學(xué)習(xí)指南[三](Arnold Robbins & Nelson H.F. Beebe著)》,本文關(guān)鍵詞  shell,腳本,學(xué)習(xí)指南,三,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《shell腳本學(xué)習(xí)指南[三](Arnold Robbins & Nelson H.F. Beebe著)》相關(guān)的同類信息!
  • 本頁收集關(guān)于shell腳本學(xué)習(xí)指南[三](Arnold Robbins & Nelson H.F. Beebe著)的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章