可以通過使用shell使大量的任務自動化,shell特別擅長系統管理任務,尤其適合那些易用性、可維護性和便攜性比效率更重要的任務。
下面,讓我們一起來看看shell是如何工作的:
建立一個指令碼
Linux中有好多中不同的shell,但是通常我們使用bash (bourne again shell) 進行shell編程,因為bash是免費的並且很容易使用。所以在本文中筆者所提供的指令碼都是使用bash(但是在大多數情況下,這些指令碼同樣可以在 bash的大姐,bourne shell中運行)。
如同其他語言一樣,通過我們使用任意一種文字編輯器,比如nedit、kedit、emacs、vi等來編寫我們的shell程式。程式必須以下面的行開始(必須方在檔案的第一行):
#!/bin/sh
符號#!用來告訴系統它後面的參數是用來執行該檔案的程式。在這個例子中我們使用/bin/sh來執行程式。當編輯好指令碼時,如果要執行該指令碼,還必須使其可執行。
要使指令碼可執行:
chmod +x filename
然後,您可以通過輸入: ./filename 來執行您的指令碼。
注釋
在進行shell編程時,以#開頭的句子表示注釋,直到這一行的結束。我們真誠地建議您在程式中使用注釋。如果您使用了注釋,那麼即使相當長的時間內沒有使用該指令碼,您也能在很短的時間內明白該指令碼的作用及工作原理。
變數
在其他程式設計語言中您必須使用變數。在shell編程中,所有的變數都由字串組成,並且您不需要對變數進行聲明。要賦值給一個變數,您可以這樣寫:
變數名=值
取出變數值可以加一個貨幣符號($)在變數前面:
#!/bin/sh
#對變數賦值:
a="hello world"
# 現在列印變數a的內容:
echo "A is:"
echo $a
在您的編輯器中輸入以上內容,然後將其儲存為一個檔案first。之後執行chmod +x first。使其可執行,最後輸入./first執行該指令碼。
這個指令碼將會輸出:
A is:
hello world
有時候變數名很容易與其他文字混淆,比如:
num=2
echo "this is the $numnd"
這並不會列印出"this is the 2nd",而僅僅列印"this is the ",因為shell會去搜尋變數numnd的值,但是這個變數時沒有值的。可以使用花括弧來告訴shell我們要列印的是num變數:
num=2
echo "this is the $nd"
這將列印: this is the 2nd
有許多變數是系統自動設定的,這將在後面使用這些變數時進行討論。
如果您需要處理數學運算式,那麼您需要使用諸如expr等程式(見下面)。除了一般的僅在程式內有效shell變數以外,還有環境變數。由export關鍵字處理過的變數叫做環境變數。我們不對環境變數進行討論,因為通常情況下僅僅在登入指令檔中使用環境變數。
Shell命令和流程式控制制
在shell指令碼中可以使用三類命令:
1)Unix 命令:
雖然在shell指令碼中可以使用任意的unix命令,但是還是由一些相對更常用的命令。這些命令通常是用來進行檔案和文字操作的。
常用命令文法及功能:
echo "some text": 將文字內容列印在螢幕上。
ls: 檔案清單。
wc –l file wc -w file wc -c file: 計算檔案行數 計算檔案中的單詞數 計算檔案中的字元數。
cp sourcefile destfile: 檔案拷貝。
mv oldname newname : 重新命名檔案或移動檔案。
rm file: 刪除檔案。
grep 'pattern' file: 在檔案內搜尋字串比如:grep 'searchstring' file.txt
cut -b colnum file: 指定欲顯示的檔案內容約制,並將它們輸出到標準輸出裝置比如:輸出每行第5個到第9個字元cut –b 5-9 file.txt千萬不要和cat命令混淆,這是兩個完全不同的命令。
cat file.txt: 輸出檔案內容到標準輸出裝置(螢幕)上。
file somefile: 得到檔案類型。
read var: 提示使用者輸入,並將輸入賦值給變數。
sort file.txt: 對file.txt檔案中的行進行排序。
uniq: 刪除文字檔中出現的行列比如: sort file.txt | uniq。
expr: 進行數學運算Example: add 2 and 3 expr 2 "+" 3。
find: 搜尋檔案比如:根據檔案名稱搜尋find . -name filename -print。
tee: 將資料輸出到標準輸出裝置(螢幕) 和檔案比如:somecommand | tee outfile。
basename file: 返回不包含路徑的檔案名稱比如: basename /bin/tux將返回 tux。
dirname file: 返迴文件所在路徑比如:dirname /bin/tux將返回 /bin。
head file: 列印文字檔開頭幾行。
tail file : 列印文字檔末尾幾行。
sed: Sed是一個基本的尋找替換程式。可以從標準輸入(比如命令管道)讀入文本,並將結果輸出到標準輸出(螢幕)。該命令採用Regex(見參考)進行搜尋。不要和shell中的萬用字元相混淆。比如:將linuxfocus 替換為 LinuxFocus :cat text.file | sed 's/linuxfocus/LinuxFocus/' > newtext.file。
awk: awk 用來從文字檔中提取欄位。預設地,欄位分割符是空格,可以使用-F指定其他分割符。cat file.txt | awk -F, '{print "," }'這裡我們使用,作為欄位分割符,同時列印第一個和第三個欄位。如果該檔案內容如下:Adam Bor, 34, IndiaKerry Miller, 22, USA
命令輸出結果為:
Adam Bor, IndiaKerry Miller.
2) 概念: 管道, 重新導向和 backtick
這些不是系統命令,但是他們真的很重要。
管道 (|) 將一個命令的輸出作為另外一個命令的輸入。
grep "hello" file.txt | wc -l
在file.txt中搜尋包含有”hello”的行並計算其行數。在這裡grep命令的輸出作為wc命令的輸入。當然您可以使用多個命令。
重新導向:將命令的結果輸出到檔案,而不是標準輸出(螢幕)。
> 寫入檔案並覆蓋舊檔案。
>> 加到檔案的尾部,保留舊檔案內容。
反短斜線,使用反短斜線可以將一個命令的輸出作為另外一個命令的一個命令列參數。
命令:
find . -mtime -1 -type f -print
用來尋找過去24小時(-mtime –2則表示過去48小時)內修改過的檔案。如果您想將所有尋找到的檔案打一個包,則可以使用以下指令碼:
#!/bin/sh
# The ticks are backticks (`) not normal quotes ('):
tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print`
3) 流程式控制制
"if" 運算式 如果條件為真則執行then後面的部分:
if ....; then
________________________________________________________________________________________________________
Shell基本文法
像進階程式設計語言一樣,Shell也提供說明和使用變數的功能。對Shell來講,所有變數的取值都是一個字串,Shell程式採用$var的形式來引用名為var的變數的值。
Shell有以下幾種基本類型的變數。
(1)Shell定義的環境變數:
Shell在開始執行時就已經定義了一些和系統的工作環境有關的變數,使用者還可以重新定義這些變數,常用的Shell環境變數有:
HOME 用於儲存註冊目錄的完全路徑名。
PATH 用於儲存用冒號分隔的目錄路徑名,Shell將按PATH變數中給出的順序搜尋這些目錄,找到的第一個與命令名稱一致的可執行檔將被執行。
TERM 終端的類型。
UID 目前使用者的識別字,取值是由數位構成的字串。
PWD 當前工作目錄的絕對路徑名,該變數的取值隨cd命令的使用而變化。
PS1 主提示符,在特權使用者下,預設的主提示符是#,在普通使用者下,預設的主提示符是$。
PS2 在Shell接收使用者輸入命令的過程中,如果使用者在輸入行的末尾輸入“\”然後斷行符號,或者當使用者按斷行符號鍵時Shell判斷出使用者輸入的命令沒有結束時,就顯示這個輔助提示符,提示使用者繼續輸入命令的其餘部分,預設的輔助提示符是>。
(2)使用者定義的變數:
使用者可以按照下面的文法規則定義自己的變數:
變數名=變數值
要注意的一點是,在定義變數時,變數名前不應加符號$,在引用變數的內容時則應在變數名前加$;在給變數賦值時,等號兩邊一定不能留空格,若變數中本身就包含了空格,則整個字串都要用雙引號括起來。
在編寫Shell程式時,為了使變數名和命令名相區別,建議所有的變數名都用大寫字母來表示。
有時我們想要在說明一個變數並對它設定為一個特定值後就不在改變它的值時,可以用下面的命令來保證一個變數的唯讀性:
readonly 變數名
在任何時候,建立的變數都只是當前Shell的局部變數,所以不能被Shell啟動並執行其他命令或Shell程式所利用,而export命令可以將一個局部變數提供給Shell執行的其他命令使用,其格式為:
export 變數名
也可以在給變數賦值的同時使用export命令:
export 變數名=變數值
使用export說明的變數,在Shell以後啟動並執行所有命令或程式中都可以訪問到。
(3)位置參數:
位置參數是一種在調用Shell程式的命令列中按照各自的位置決定的變數,是在程式名之後輸入的參數。位置參數之間用空格分隔,Shell取第一個位置參數替換程式檔案中的$1,第二個替換$2,依次類推。$0是一個特殊的變數,它的內容是當前這個Shell程式的檔案名稱,所以,$0不是一個位置參數,在顯示當前所有的位置參數時是不包括$0的。
(4)預定義變數:
預定義變數和環境變數相類似,也是在Shell一開始時就定義了的變數。所不同的是,使用者只能根據Shell的定義來使用這些變數,而不能重定義它。所有預定義變數都是由$符和另一個符號組成的,常用的Shell預定義變數有:
$# 位置參數的數量。
$* 所有位置參數的內容。
$? 命令執行後返回的狀態。
$$ 當前進程的進程號。
$! 後台啟動並執行最後一個進程號。
$0 當前執行的進程名。
其中,$?用於檢查上一個命令執行是否正確。(在Linux中,命令退出狀態為0表示該命令正確執行,任何非0值表示命令出錯。)
$$變數最常見的用途是用做暫存檔案的名字以保證暫存檔案不會重複。
(5)參數置換的變數:
Shell提供了參數置換功能以便使用者可以根據不同的條件來給變數賦不同的值。參數置換的變數有4種,這些變數通常與某一個位置參數相聯絡,根據指定的位置參數是否已經設定類決定變數的取值,它們的文法和功能分別如下。
a. 變數=${參數-word}:如果設定了參數,則用參數的值置換變數的值,否則用word置換。即這種變數的值等於某一個參數的值,如果該參數沒有設定,則變數就等於word的值。
b. 變數=${參數=word}:如果設定了參數,則用參數的值置換變數的值,否則把變數設定成word,然後再用word替換參數的值。注意,位置參數不能用於這種方式,因為在Shell程式中不能為位置參數賦值。
c. 變數=${參數?word}:如果設定了參數,則用參數的值置換變數的值,否則就顯示word並從Shell中退出,如果省略了word,則顯示標準資訊。這種變數要求一定等於某一個參數的值。如果該參數沒有設定,就顯示一個資訊,然後退出,因此這種方式常用於出錯指示。
d. 變數=${參數+word}:如果設定了參數,則用word置換變數,否則不進行置換。
所有這4種形式中的“參數”既可以是位置參數,也可以是另一個變數,只是用位置參數的情況比較多。
Shell程式設計的流程式控制制
和其他進階程式設計語言一樣,Shell提供了用來控製程序執行流程的命令,包括條件分支和迴圈結構,使用者可以用這些命令建立非常複雜的程式。
與傳統語言不同的是,Shell用於指定條件值的不是布爾運算式,而是命令和字串。
1.測試命令
test命令用於檢查某個條件是否成立,它可以進行數值、字元和檔案3個方面的測試,其測試符和相應的功能分別如下。
(1)數值測試:
-eq 等於則為真。
-ne 不等於則為真。
-gt 大於則為真。
-ge 大於等於則為真。
-lt 小於則為真。
-le 小於等於則為真。
(2)字串測試:
= 等於則為真。
!= 不相等則為真。
-z字串 字串長度偽則為真。
-n字串 字串長度不偽則為真。
(3)檔案測試:
-e檔案名稱 如果檔案存在則為真。
-r檔案名稱 如果檔案存在且可讀則為真。
-w檔案名稱 如果檔案存在且可寫則為真。
-x檔案名稱 如果檔案存在且可執行則為真。
-s檔案名稱 如果檔案存在且至少有一個字元則為真。
-d檔案名稱 如果檔案存在且為目錄則為真。
-f檔案名稱 如果檔案存在且為普通檔案則為真。
-c檔案名稱 如果檔案存在且為字元型特殊檔案則為真。
-b檔案名稱 如果檔案存在且為塊特殊檔案則為真。
另外,Linux還提供了與(!)、或(-o)、非(-a)三個邏輯操作符,用於將測試條件串連起來,其優先順序為:!最高,-a次之,-o最低。
同時,bash也能完成簡單的算術運算,格式如下:
$[expression]
例如:
var1=2
var2=$[var1*10+1]
則var2的值為21。
2.if條件陳述式
Shell程式中的條件分支是通過if條件陳述式來實現的,其一般格式為:
if 條件命令串
then
條件為真時的命令串
else
條件為假時的命令串
fi
3.for迴圈
for迴圈對一個變數的可能的值都執行一個命令序列。賦給變數的幾個數值既可以在程式內以數值列表的形式提供,也可以在程式以外以位置參數的形式提供。for迴圈的一般格式為:
for變數名 [in數值列表]
do
若干個命令列
done
變數名可以是使用者選擇的任何字串,如果變數名是var,則在in之後給出的數值將順序替換迴圈命令列表中的$var。如果省略了in,則變數var的取值將是位置參數。對變數的每一個可能的賦值都將執行do和done之間的命令列表。
4.while和until迴圈
while和until命令都是用命令的返回狀態值來控制迴圈的。While迴圈的一般格式為:
while
若干個命令列1
do
若干個命令列2
done
只要while的“若干個命令列1”中最後一個命令的返回狀態為真,while迴圈就繼續執行do...done之間的“若干個命令列2”。
until命令是另一種迴圈結構,它和while命令相似,其格式如下:
until
若干個命令列1
do
若干個命令列2
done
until迴圈和while迴圈的區別在於:while迴圈在條件為真時繼續執行迴圈,而until則是在條件為假時繼續執行迴圈。
Shell還提供了true和false兩條命令用於建立無限迴圈結構,它們的返回狀態分別是總為0或總為非0。
5.case條件選擇
if條件陳述式用於在兩個選項中選定一項,而case條件選擇為使用者提供了根據字串或變數的值從多個選項中選擇一項的方法,其格式如下:
case string in
exp-1)
若干個命令列1
;;
exp-2)
若干個命令列2
;;
……
*)
其他命令列
esac
Shell通過計算字串string的值,將其結果依次和運算式exp-1, exp-2等進行比較,直到找到一個匹配的運算式為止。如果找到了匹配項,則執行它下面的命令直到遇到一對分號(;;)為止。
在case運算式中也可以使用Shell的萬用字元(“*”、“?”、“[ ]”)。通常用 * 作為case命令的最後運算式以便在前面找不到任何相應的匹配項時執行“其他命令列”的命令。
6.無條件控制語句break和continue
break用於立即終止當前迴圈的執行,而contiune用於不執行迴圈中後面的語句而立即開始下一個迴圈的執行。這兩個語句只有放在do和done之間才有效。
7.函數定義
在Shell中還可以定義函數。函數實際上也是由若干條Shell命令組成的,因此它與Shell程式形式上是相似的,不同的是它不是一個單獨的進程,而是Shell程式的一部分。函數定義的基本格式為:
functionname
{
若干命令列
}
調用函數的格式為:
functionname param1 param2…
Shell函數可以完成某些例行的工作,而且還可以有自己的退出狀態,因此函數也可以作為if, while等控制結構的條件。
在函數定義時不用帶參數說明,但在調用函數時可以帶有參數,此時Shell將把這些參數分別賦予相應的位置參數$1, $2, ...及$*。
8.命令分組
在Shell中有兩種命令分組的方法:()和{}。前者當Shell執行()中的命令時將再建立一個新的子進程,然後這個子進程去執行圓括弧中的命令。當使用者在執行某個命令時不想讓命令運行時對狀態集合(如位置參數、環境變數、當前工作目錄等)的改變影響到下面語句的執行時,就應該把這些命令放在圓括弧中,這樣就能保證所有的改變只對子進程產生影響,而父進程不受任何幹擾。{}用於將順序執行的命令的輸出結果用於另一個命令的輸入(管道方式)。當我們要真正使用圓括弧和花括弧時(如計算運算式的優先順序),則需要在其前面加上轉義符(\)以便讓Shell知道它們不是用於命令執行的控制所用。
9.訊號
trap命令用於在Shell程式中捕捉訊號,之後可以有3種反應方式:
(1)執行一段程式來處理這一訊號。
(2)接受訊號的預設操作。
(3)忽視這一訊號。
trap對上面3種方式提供了3種基本形式:
第一種形式的trap命令在Shell接收到與signal list清單中數值相同的訊號時,將執行雙引號中的命令串。
trap 'commands' signal-list
trap "commands" signal-list
為了恢複訊號的預設操作,使用第二種形式的trap命令:
trap signal-list
第三種形式的trap命令允許忽略訊號:
trap " " signal-list
注意:
(1)對訊號11(段違例)不能捕捉,因為Shell本身需要捕捉該訊號去進行記憶體的轉儲。
(2)在trap中可以定義對訊號0的處理(實際上沒有這個訊號),Shell程式在其終止(如執行exit語句)時發出該訊號。
(3)在捕捉到signal-list中指定的訊號並執行完相應的命令之後,如果這些命令沒有將Shell程式終止的話,Shell程式將繼續執行收到訊號時所執行的命令後面的命令,這樣將很容易導致Shell程式無法終止。
另外,在trap語句中,單引號和雙引號是不同的。當Shell程式第一次碰到trap語句時,將把commands中的命令掃描一遍。此時若 commands是用單引號括起來的話,那麼Shell不會對commands中的變數和命令進行替換,否則commands中的變數和命令將用當時具體的值來替換。
運行Shell程式的方法
使用者可以用任何編輯程式來編寫Shell程式。因為Shell程式是解釋執行的,所以不需要編譯成目的程式。按照Shell編程的慣例,以 bash為例,程式的第一行一般為“#!/bin/bash”,其中 # 表示該行是注釋,歎號 !告訴Shell運行歎號之後的命令並用文檔的其餘部分作為輸入,也就是運行/bin/bash並讓/bin/bash去執行Shell程式的內容。
執行Shell程式的方法有3種。
1.sh Shell程式檔案名稱
這種方法的命令格式為:
bash Shell程式檔案名稱
這實際上是調用一個新的bash命令解釋程式,而把Shell程式檔案名稱作為參數傳遞給它。新啟動的Shell將去讀指定的檔案,可執行檔中列出的命令,當所有的命令都執行完後結束。該方法的優點是可以利用Shell調試功能。
2.sh
格式為:
bash< Shell程式名
這種方式就是利用輸入重新導向,使Shell命令解釋程式的輸入取自指定的程式檔案。
3.用chmod命令使Shell程式成為可執行檔
一個檔案能否運行取決於該文檔的內容本身可執行且該檔案具有執行權。對於Shell程式,當用編輯器產生一個檔案時,系統賦予的許可權都是644(rw-r-r--),因此,當使用者需要運行這個檔案時,只需要直接鍵入檔案名稱即可。
在這3種運行Shell程式的方法中,最好按下面的方式選擇:當剛建立一個Shell程式,對它的正確性還沒有把握時,應當使用第一種方式進行調試。當一個Shell程式已經調試好時,應使用第三種方式把它固定下來,以後只要鍵入相應的檔案名稱即可,並可被另一個程式所調用。
4.bash程式的調試
在編程過程中難免會出錯,有的時候,偵錯工具比編寫程式花費的時間還要多,Shell程式同樣如此。
Shell程式的調試主要是利用bash命令解釋程式的選擇項。調用bash的形式是:
bash -選擇項Shell程式檔案名稱
幾個常用的選擇項是:
-e 如果一個命令失敗就立即退出。
-n 讀入命令但是不執行它們。
-u 置換時把未設定的變數看做出錯。
-v 當讀入Shell輸入行時把它們顯示出來。
-x 執行命令時把命令和它們的參數顯示出來。
上面的所有選項也可以在Shell程式內部用“set -選擇項”的形式引用,而“set +選擇項”則將禁止該選擇項起作用。如果只想對程式的某一部分使用某些選擇項時,則可以將該部分用上面兩個語句包圍起來。
(1)未置變數退出和立即退出
未置變數退出特性允許使用者對所有變數進行檢查,如果引用了一個未賦值的變數就終止Shell程式的執行。Shell通常允許未置變數的使用,在這種情況下,變數的值為空白。如果設定了未置變數退出選擇項,則一旦使用了未置變數就顯示錯誤資訊,並終止程式的運行。未置變數退出選擇項為-u。
當Shell運行時,若遇到不存在或不可執行檔命令、重新導向失敗或命令非正常結束等情況時,如果未經重新定向,該出錯資訊會顯示在終端螢幕上,而Shell程式仍將繼續執行。要想在錯誤發生時迫使Shell程式立即結束,可以使用-e選項將Shell程式的執行立即終止。
(2)Shell程式的跟蹤
調試Shell程式的主要方法是利用Shell命令解釋程式的-v或-x選項來跟蹤程式的執行。-v選擇項使Shell在執行程式的過程中,把它讀入的每一個命令列都顯示出來,而-x選擇項使Shell在執行程式的過程中把它執行的每一個命令在行首用一個+加上命令名顯示出來。並把每一個變數和該變數所取的值也顯示出來。因此,它們的主要區別在於:在執行命令列之前無-v,則顯示出命令列的原始內容,而有-v時則顯示出經過替換後的命令列的內容。
除了使用Shell的-v和-x選擇項以外,還可以在Shell程式內部採取一些輔助調試的措施。例如,可以在Shell程式的一些關鍵地方使用echo命令把必要的資訊顯示出來,它的作用相當於C語言中的printf語句,這樣就可以知道程式運行到什麼地方及程式目前的狀態。
bash的內部命令
bash命令解釋套裝程式包含了一些內部命令。內部命令在目錄列表時是看不見的,它們由Shell本身提供。常用的內部命令有:echo, eval, exec, export, readonly, read, shift, wait和點(.)。下面簡單介紹其命令格式和功能。
1.echo
命令格式:echo arg
功能:在螢幕上顯示出由arg指定的字串。
2.eval
命令格式:eval args
功能:當Shell程式執行到eval語句時,Shell讀入參數args,並將它們組合成一個新的命令,然後執行。
3.exec
命令格式:exec命令參數
功能:當Shell執行到exec語句時,不會去建立新的子進程,而是轉去執行指定的命令,當指定的命令執行完時,該進程(也就是最初的Shell)就終止了,所以Shell程式中exec後面的語句將不再被執行。
4.export
命令格式:export變數名 或:export變數名=變數值
功能:Shell可以用export把它的變數向下帶入子Shell,從而讓子進程繼承父進程中的環境變數。但子Shell不能用export把它的變數向上帶入父Shell。
注意:不帶任何變數名的export語句將顯示出當前所有的export變數。
5.readonly
命令格式:readonly變數名
功能:將一個使用者定義的Shell變數標識為不可變。不帶任何參數的readonly命令將顯示出所有唯讀Shell變數。
6.read
命令格式:read變數名表
功能:從標準輸入裝置讀入一行,分解成若干字,賦值給Shell程式內部定義的變數。
7.shift語句
功能:shift語句按如下方式重新命名所有的位置參數變數,即$2成為$1,$3成為$2…在程式中每使用一次shift語句,都使所有的位置參數依次向左移動一個位置,並使位置參數$#減1,直到減到0為止。
8.wait
功能:使Shell等待在後台啟動的所有子進程結束。wait的傳回值總是真。
9.exit
功能:退出Shell程式。在exit之後可有選擇地指定一個數位作為返回狀態。
10.“.”(點)
命令格式:. Shell程式檔案名稱
功能:使Shell讀入指定的Shell程式檔案並依次執行檔案中的所有語句。