Unix系列shell程序編寫(上)
*Shell是什麼?
任何發明都具有供用戶使用的界面。UNIX供用戶使用的界面就是Shell(DOS的command熟悉吧,但UNIX的要強大的多)。 Shell為用戶提供了輸入命令和參數並可得到命令執行結果的環境。
為了不同的需要,UNIX提供了不同的Shell。現在的UNIX大部分都支持BourneShell,以下教程就以BourneShell(Bsh)為例,一步步的領略UNIX Shell的強大功能,占先其強大魅力,達到更方便靈活的管理、應用UNIX的目的。
1.UNIX內核和Shell的交互方法
啟動UNIX時,程序UNIX(內核)將被調入計算機內存,並一直保留在內存中直到機器關閉。在引導過程中,程序 init將進入後台運行一直到機器關閉。該程序查詢文件/etc/inittab,該文件列出了連接終端的各個端口及其特征。當發現一個活動的終端時,init程序調用getty程序在終端上顯示login等登陸信息。(username和passwd),在輸入密碼後, getty調用login進程,該進程根據文件/etc/passwd的內容來驗證用戶的身份。若用戶通過身份驗證,login進程 把用戶的home目錄設置成當前目錄並把控制交給一系列setup程序。setup程序可以是指定的應用程序,通常setup程序 為一個Shell程序,如:/bin/sh 即Bourne Shell(command出來了,呵呵)。
得到控制後,Shell程序讀取並執行文件/etc/.profile以及.profile。這兩個文件分別建立了系統范圍內的和 該用戶自己的工作環境。最後Shell顯示命令提示符,如$。(這是以bsh為例,若是csh,為.cshrc,ksh為.kshrc,bash為.bashrc等等)
注:(不妨把/etc/.profile和.profile看成DOS的autoexec.bat 或 config.sys文件)
當shell退出時,內核把控制交給init程序,該程序重新啟動自動登陸過程。有兩種方法使shell退出,一是用戶執行exit命令,二是 內核(例如root用kill命令)發出一個kill命令結束shell進程。shell退出後,內核回收用戶及程序使用的資源。
用戶登陸後,用戶命令同計算機交互的關系為:命令進程--->Shell程序--->UNIX內核--->計算機硬件。當用戶輸入一個命令,如$ls, Shell將定位其可執行文件/bin/ls並把其傳遞給內核執行。內核產生一個新的子進程調用並執行/bin/ls。當程序執行完畢後,內核取消 該子進程並把控制交給其父進程,即Shell程序。例如執行:
$ps
該命令將會列出用戶正在執行的進程,即Shell程序(下來詳細說說,別急現在)和ps程序。若執行:
$sleep 10 &
$ps
其中第一條命令將產生一個在後台執行的sleep子進程。ps命令執行時會顯示出該子進程。
每當用戶執行一條命令時,就會產生一個子進程。該子進程的執行與其父進程或Shell完全無關,這樣可以使Shell去做其他工作。(Shell只是把用戶的意圖告訴內核,然後該干嘛干嘛:)) 現在windows有個計劃任務(在固定的時間,日期自動執行某任務),其實UNIX很早就有這個功能了,也就是所謂的Shell的自動執行。一些UNIX 資源,如cron可以自動執行Shell程序而無需用戶的參與,(這個功能好象在/var/spool/crotab目錄裡)。 Crontab 程序對於系統管理員來說是非常有用的。Cron 服務用於計劃程序在特定時間(月、日、周、時、分)運行。我們以root的crontab 為例。根用戶的 crontab 文件放在 /var/spool/crontab/root 中,其格式如下:
(1)
(2)
(3)
(4)
(5)
(6)
0
0
*
*
3
/usr/bin/updatedb
1. 分鐘 (0-60)
2. 小時 (0-23)
3. 日 (1-31)
4. 月 (1-12)
5. 星期 (1-7)
6. 所要運行的程序
2.Shell的功能和特點
1>命令行解釋
2>使用保留字
3>使用Shell元字符(通配符)
4>可處理程序命令
5>使用輸入輸出重定向和管道
6>維護一些變量
7>運行環境控制
8>支持Shell編程
對於"命令行解釋"就不多說了,就是在shell提示符(例如:"$","%","#"等)後輸入一行unix命令,Shell將接收用戶的輸入。
"使用保留字":Shell有一些具有特殊意義的字,例如在Shell腳本中,do,done,for等字用來控制循環操作,if,then等控制條件操作。 保留字隨Shell環境的不同而不同。
"通配符":* 匹配任何位置
? 匹配單個字符
[] 匹配的字符范圍或列表 例如:
$ls [a-c]*
將列出以a-c范圍內字符開頭的所有文件
$ls [a,m,t]*
將列出以e,m或t開頭的所有文件
"程序命令" :當用戶輸入命令後,Shell讀取環境變量$path(一般在用戶自己的.profile中設置),該變量包含了命令可執行文件可能存在的目錄列表。 shell從這些目錄中尋找命令所對應的可執行文件,然後將該文件送給內核執行。
"輸入輸出重定向及管道" :重定向的功能同DOS的重定向功能:
">" 重定向輸出
"<" 重定向輸入
而管道符號,是unix功能強大的一個地方,符號是一條豎線:"|",用法: command 1 | command 2 他的功能是把第一個命令command 1執行的結果作為command 2的輸入傳給command 2,例如:
$ls -s|sort -nr|pg
該命令列出當前目錄中的所有文件,並把輸出送給sort命令作為輸入,sort命令按數字遞減的順序把ls的輸出排序。然後把排序後的 內容傳送給pg命令,pg命令在顯示器上顯示sort命令排序後的內容。
"維護變量" :Shell可以維護一些變量。變量中存放一些數據供以後使用。用戶可以用"="給變量賦值,如:
$lookup=/usr/mydir
該命令建立一個名為lookup的變量並給其賦值/usr/mydir,以後用戶可以在命令行中使用lookup來代替/usr/mydir,例如:
$echo $lookup
結果顯示:/usr/mydir
為了使變量能被子進程使用,可用exprot命令,例如:
$lookup=/usr/mydir
$export lookup
"運行環境控制" :當用戶登陸啟動shell後,shell要為用戶創建一個工作的環境,如下:
1>當login程序激活用戶shell後,將為用戶建立環境變量。從/etc/profile和.profile文件中讀出,在這些文件中一般都用$TERM 變量設置終端類型,用$PATH變量設置Shell尋找可執行文件的路徑。
2>從/etc/passwd文件或命令行啟動shell時,用戶可以給shell程序指定一些參數,例如"-x",可以在命令執行前顯示該命令及其參數。後面詳細介紹這些參數。
"shell編程" :本文主要介紹的內容。
shell本身也是一種語言(*可以先理解為unix命令的組合,加上類C的條件,循環等程序控制語句,類似dos批處理,但要強大的多),用戶可以 通過shell編程(腳本,文本文件),完成特定的工作。
SHELL變量
下面我們詳細的介紹Bourne Shell的編程:
自從貝爾實驗室設計了Bourne Shell。從那時起許多廠商根據不同的硬件平台設計了許多版本得unix。但在眾多版本的unix中,Bourne Shell 一直保持一致。
1>Bsh的啟動:用戶在登陸後,系統根據文件/etc/passwd中有關該用戶的信息項啟動Shell。例如某用戶在passwd中 的信息項為:
ice_walk:!:411:103:Imsnow ,ice_walk:/home/ice_walk:/bin/bsh
則表明,用戶名是ice_walk等信息,在最後一項"/bin/bsh"表明用戶的sh環境類型是bsh,於是系統啟動之。在啟動或執行(包括下面我們要講 的shell程序--腳本)過程中可以使用以下一些參數,我們一一說明:
-a 將所有變量輸出
-c "string"從string中讀取命令
-e 使用非交互式模式
-f 禁止shell文件名產生
-h 定義
-i 交互式模式
-k 為命令的執行設置選項
-n 讀取命令但不執行
-r 受限模式
-s 命令從標准輸入讀取
-t 執行一命令,然後退出shell
-u 在替換時,使用未設置的變量將會出錯
-v 顯示shell的輸入行
-x 跟蹤模式,顯示執行的命令
許多模式可以組合起來用,您可以試試了,但-ei好象不行,你說why呢?
使用set可以設置或取消shell的選項來改變shell環境。打開選項用"-",關閉選項用"+",多數unix允許打開或關閉a、f、e、h、k、n、 u、v和x選項。若顯示Shell中已經設置的選項,執行:
$echo $-
Bsh中每個用戶的home目錄下都有一個.profile文件,可以修改該文件來修改shell環境。為了增加一個可執行文件的路徑(例如/ice_walk/bin), 可以把下面代碼加入.profile中
PATH=$PATH:/ice_walk/bin;exprot PATH
.profile中shell的環境變量意思如下:
CDPATH 執行cd命令時使用的搜索路徑
HOME 用戶的home目錄
IFS 內部的域分割符,一般為空格符、制表符、或換行符
MAIL 指定特定文件(信箱)的路徑,有UNIX郵件系統使用
PATH 尋找命令的搜索路徑(同dos的config.sys的 path)
PS1 主命令提示符,默認是"$"
PS2 從命令提示符,默認是">"
TERM 使用終端類型
2>Bsh裡特殊字符及其含義
在Bsh中有一組非字母字符。這些字符的用途分為四類:作為特殊變量名、產生文件名、數據或程序控制以及引用和逃逸字符控制。他們 可以讓用戶在Shell中使用最少的代碼完成復雜的任務。
*> Shell變量名使用的特殊字符
$# 傳送給命令Shell的參數序號
$- 在Shell啟動或使用set命令時提供選項
$? 上一條命令執行後返回的值
$$ 當前shell的進程號
$! 上一個子進程的進程號
$@ 所有的參數,每個都用雙括號括起
$* 所有參數,用雙括號括起
$n 位置參數值,n表示位置
$0 當前shell名
*>產生文件名的特殊字符
包括"*","?","[]",上面講過,不再多說。
*>數據或程序控制使用的特殊字符
>(file) 輸出重定向到文件中(沒有文件則創建,有則覆蓋)
>>(file) 輸出重定向到文件中(沒有則創建,有則追加到文件尾部)
<(file) 輸入重定向到文件
; 命令分割符
| 管道符
& 後台運行(例如:sleep 10 &)
` ` 命令替換,重定向一條命令的輸出作為另一命令的參數
*>對於引用或逃逸的特殊字符
Bsh用單引號' '和雙引號" "將特殊字符或由空白分隔的字引用起來組成一個簡單的數據串.使用單引號和雙引號的區別是雙引號中的內容可進行參數和變量替換.逃逸字符也一樣.
$echo "$HOME $PATH"
結果顯示$/u/ice_walk/bin:/etc:/usr/bin
而$echo '$HOME $PATH' 結果顯示$HOME $PATH
shell的逃逸符是一個"",表示其後的字符不具有特殊的含義或不是shell的函數
$echo $HOME $PATH
結果顯$$HOME /bin:/etc:/usr/bin:
3>Bsh的變量
前面我們在多個地方引用了變量,當Shell遇到一個"$"符時(沒有被引用或逃逸),它將認為其後為一變量。不論該變量是環境變量還是用戶自定義的變量,在命令行中變量名要被變量值替換。例如命令:ls $HOME將列出變量HOME對應目錄下的文件。 用戶可以在命令行中的任何地方進行變量替換。包括命令名本身,例如:
$dir=ls
$$dir f*
將列出以f開頭的文件。
現在詳細的介紹下Bsh的變量。Bsh中有四類變量:用戶定義的變量、位置變量(shell參數)、預定義變量及環境變量。
用戶定義的變量:
用戶定義的變量由字母和下劃線組成,並且變量名的第一個字符不能為數字(0~9)。與其他UNIX名字一樣,變量名是大小寫敏感的。用戶可以在命令行上用"="給變量賦值,例如:
$NAME=ice_walk
給變量NAME賦值為ice_walk,在應用變量NAME的時候,在NAME前加"$"即可,前面已說,不再廢話(別說我廢話多,關鍵是沒當過老師:()。可以用變量和其他字符組成新的字,例如:
$SUN=sun
$echo ${SUN}day
在應用shell變量時候,可以在變量名字兩邊$後面加上{},以更加清楚的顯示給shell,哪個是真正的變量,以實現字符串的合並等功能。
結果顯示:sunday(注意不能echo $SUNday,因為SUNday變量沒定義,讀者試下執行結果) 用戶也可以在命令行上同時對多個變量賦值,賦值語句之間用空格分開:
$X=x Y=y
注意變量賦值是從右到左進行的
$X=$Y Y=y
X的值是y
$X=z Y=$Z
Y的值是空(變量未賦值時,shell不報錯,而是賦值為空)
用戶可以使用"unset <變量>"命令清除給變量賦的值
用戶使用變量時要在其前面加一"$"符,使變量名被變量值所替換。Bsh可以進行變量的條件替換,即只有某種條件發生時才進行替換。替換條件放在一對大括號{}中,如:
${variable: -value} variable是一變量值,value是變量替換使用的默認值
$echo Hello $UNAME
結果顯示:Hello
$echo Hello ${UNAME: -there}
結果顯示:Hello there
$echo $UNAME
結果顯示: (空)
$UNAME=John
$echo Hello ${UNAME: -there}
結果顯示:Hello John
可以看出,變量替換時將使用命令行中定義的默認值,但變量的值並沒有因此而改變。另外一種替換的方法是不但使用默認值進行替換,而且將默認值賦給該變量。其形式如下:
${variable:=value}
該形式在變量替換後同時把值value符給變量variable。
$echo Hello $UNAME
結果顯示:Hello
$echo Hello ${UNAME:=there}
結果顯示:Hello there
$echo $UNAME
結果顯示:there
$UNAME=John
$echo Hello ${UNAME:-there}
結果顯示:Hello John
變量替換的值也可以是` `括起來的命令:
$USERDIR={$Mydir: -`pwd`}
第三種變量的替換方法是只有當變量已賦值時才用指定值替換形式:
${variable: +value}
只有變量variable已賦值時,其值才用value替換,否則不進行任何替換,例如:
$ERROPT=A
$echo ${ERROPT: +"Error tracking is acitive"}
結果顯示:Error tracking is acitive
$ERROPT=
$echo ${ERROPT: +"Error tracking is acitive"}
結果顯示: (空)
我們還可以使用錯誤檢查的條件進行變量替換:
${variable:?message}
當變量variable已設置時,正常替換。否則消息message將送到標准錯誤輸出(若此替換出現在shell程序中,那麼該程序將終止)。
例如:
$UNAME=
$echo $ {UNAME:?"UNAME HAS NOT BEEN SET"}
結果顯示:UNAME HAS NOT BEEN SET
$UNAME=Stephanie
$echo $ {UNAME:?"UNAME HAS NOT BEEN SET"}
結果顯示:Stephanie
當沒有指定message時,shell將顯示一條默認的消息,例如:
$UNAME=
$echo $ {UNAME:?}
結果顯示:sh:UNAME:parameter null or not set
4>位置變量或Shell參數
在shell解釋用戶的命令時,將把命令行的第一個字作為命令,而其他的字作為參數。當命令對應的可執行文件為Shell程序時,這些參數將作為位置變量傳送給該程序。第一個參數記為$1,第二個為$2....第九個為$9。其中1到9是真正的參數名,"$"符只是用來標識變量的替換。
位置變量$0指命令對應的可執行文件名。在後面將詳細介紹位置變量。
1.只讀變量
用戶將變量賦值後,為了防止以後對該變量的修改,可以用以下命令將該變量設置為只讀變量:
readonly variable
2.export命令
shell執行一個程序時,首先為該程序建立一個新的執行環境,稱為子shell。在Bourne Shell中變量都是局部的,即他們只在創建他們的Shell中有意義。用戶可以用export命令讓變量被其他子Shell識別。但某用戶的變量是沒法讓其他用戶使用的。
當用戶啟動一個新shell時,該shell將使用默認的提示符。因為賦給變量PS1的值只在當前shell中有效。為了讓子Shell使用當前Shell中定義的提示符號,可以使用export命令:
$PS1="Enter command:"
Enter command:export PS1
Enter command:sh
Enter command:
此時變量PS1變成了全局變量。它可以被其子Shell使用。當變量被設置成全局的以後,將一直保持有效直到用戶退出該變量所在的Shell。用戶可以在文件.profile中給一個變量永久賦值。詳見"規范Shell"。
基本語句
從本節起,我們將詳細介紹Shell程序設計的基本知識,通過編寫Shell腳本,用戶可以根據自己的需要有條件的或者重復的執行命令。通過Shell程序,可以把單個的UNIX命令組合成一個完全實用的工具,完成用戶的任務。
1>什麼是Shell程序
當用戶在UNIX Shell中輸入了一條復雜的命令,如:
$ls -R /|greo myname |pg
我們可以稱用戶在對Shell編程,當把這條語句寫在一個文件裡,並且符給該文件可執行權限,那麼該文件就是我們傳統上說的Shell程序。
2>簡單的Shell程序
假設用戶每天使用下述命令備份自己的數據文件:
$cd /usr/icewalk;ls * |cpio -o > /dev/fd0
我們可以把它寫在一個文件,如:ba.sh中:
$cat >ba.sh
cd /usr/icewalk
ls * |cpio -o > /dev/fd0
^D
(ctrl_d)
程序ba.sh就是Shell腳本,用戶可以用vi或其他編輯工具編寫更復雜的腳本。
此時用戶備份文件只需要執行Shell程序ba.sh,執行時需在當前Shell中創建一個子Shell:
$sh ba.sh
程序sh與用戶登陸時執行的Bourne Shell相同,但當Sh命令帶參數ba.sh後,它將不再是一個交互式的Shell,而是直接從文件ba.sh中讀取命令。
執行ba.sh中命令的另一方法是給文件ba.sh執行權限:
$chmod +x ba.sh
此時,用戶可以輸入文件名ba.sh做為一個命令來備份自己的數據,需要注意的是,用這種方法執行命令的時候,文件ba.sh必須存在於環境變量$PATH所指定的路徑上。
Unix系列shell程序編寫(中)
3>在Shell中使用數據變量
用戶可以在Shell中使用數據變量,例如ba.sh程序:
cd/usr/icewalk
ls|cpio -o > /dev/fd0
該程序中要備份的目錄為一常量,即該程序只能用來備份一個目錄。若在該程序中使用變量,則會使其更通用:
workdir=$1
cd $workdir
ls * |cpio -o > /dev/fd0
通過這一改變,用戶可以使用程序備份變量$workdir指定的目錄。例如我們要備份/home/www的內容,只要運行ba.sh /home/www即可實現。(若不明白 $1,下面將詳細介紹shell參數的傳遞,$1代表本sh程序-ba.sh的第一個參數)
4>在Shell程序中加上注釋
為了增加程序的可讀性,我們提倡加入注釋。在Shell程序中注釋將以"#"號開始。當Shell解釋到"#"時,會認為從"#"號起一直到該行行尾為注釋。
5>對Shell變量進行算術運算
高級語言中變量是具有類型的,即變量將被限制為某一數據類型,如整數或字符類型。Shell變量通常按字符進行存儲,為了對Shell變量進行算術運算,必須使用expr命令。
expr命令將把一個算術表達式作為參數,通常形式如下:
expr [數字] [操作符] [數字]
由於Shell是按字符形式存儲變量的,所以用戶必須保證參加算術運算的操作數必須為數值。下面是有效的算術操作符:
+
兩個整數相加
-
第一個數減去第二個數
*
兩整數相乘
/
第一個整數除以第二個整數
%
兩整數相除,取余數
例如:
$expr 2 + 1
結果顯示:3
$expr 5 - 3
結果顯示:2若expr的一個參數是變量,那麼在表達式計算之前用變量值替換變量名。
$int=3
$expr $int + 4
結果顯示:7
用戶不能單純使用"*"做乘法,若輸入:
$expr 4*5
系統將會報錯,因為Shell看到"*"將會首先進行文件名替換。正確形式為:
$expr 4 * 5
結果顯示:20
多個算術表達式可以組合在一起,例如:
$expr 5 + 7 / 3
結果顯示:7
運算次序是先乘除後加減,若要改變運算次序,必須使用"`"號,如:
$int=`expr 5 + 7`
$expr $int/3
結果顯示:4
或者:
$expr `expr 5+7`/3
結果顯示:4
6>向Shell程序傳遞參數
一個程序可以使用兩種方法獲得輸入數據。一是執行時使用參數。另一種方法是交互式地獲得數據。vi編輯程序可以通過交互式的方法獲得數據,而ls和expr則從參數中取得數據。以上兩種方法Shell程序都可以使用。在"交互式讀入數據"一節中將介紹Shell程序通過交互式的方法獲得參數。
通過命令行給Shell程序傳遞參數可以擴大程序的用途。以前面提到的ba.sh程序為例:
$cat >re.sh
cd $workdir
cpio -i < /dev/fd0
^d
程序re.sh恢復了ba.sh程序備份的所有文件。若只從軟盤上恢復一個指定的文件,可以用該文件名作為參數,傳遞給Shell程序re.sh:
程序改寫如下:
$cat >re2.sh
cd $workdir
cpio -i $1 < /dev/fd0
^d
用戶可以指定要恢復的文件,例如fname
$re2.sh fname
此時文件fname作為第一個位置參數傳遞給re2.sh,re2.sh的缺點是要恢復兩個或多個文件要重復運行,我們可以用$*變量傳遞不確定的參數給程序:
$cat >re3.sh
cd $workdir
cpio -i $* < /dev/fd0
^d
我們就可以恢復多個文件,例如fname1,fname2,fname3
$re3.sh fname1 fname2 fname3
(以上程序re.sh,re2.sh,re3.sh,假設用戶已經chmod了可執行權利)
因為沒有賦值的變量可以作為NULL看待,所以若是程序re3.sh在執行時候沒賦予參數,那麼一個空值將被插入到cpio命令中。該命令將恢復所有保存的文件。
條件判斷語句
條件判斷語句是程序設計語言中十分重要的語句,該語句的含義是當某一條件滿足時,執行指定的一組命令。
1>if - then語句
格式: if command1
then
command2
command3
fi
---(if 語句結束)
command4
每個程序或命令執行結束後都有一個返回的狀態,用戶可以用Shell變量$?獲得這一狀態。if語句檢查前面命令執行的返回狀態,若該命令成功執行,那麼在then和fi之間的命令都將被執行。在上面的命令序列中,command1和command4總要執行。若command1成功執行,command2和command3也將執行。
請看下面程序:
#unload -program to backup and remove files
cd $1
ls -a | cpio -o > /dev/mnt0
rm *
該程序在備份資料後,刪除檔案,但當cpio命令不能成功執行時,rm命令還是把資料刪除了,我們可不希望這樣,為了避免此情況,可以用if - then語句:
#--卸載和判斷刪除程序
cd $1
if ls -a | cpio > /dev/mnt0
then
rm *
fi
上面程序在cpio執行成功後才刪除檔案
同時,若執行沒有成功,我們希望得到提示,sh中的echo命令可以向用戶顯示消息,並顯示後換行,上面程序可以寫成:
#--卸載和判斷刪除程序
cd $1
if ls -a | cpio > /dev/mnt0
then
echo "正刪除文件資料... ..."
rm *
fi
echo命令可以使用一些特殊的逃逸字符進行格式化輸出,下面是這些字符及其含義:
Backspace
c
顯示後不換行
f
在終端上屏幕的開始處顯示
換行
回車
制表符
v
垂直制表符
反斜框