天天看點

Shell程式設計基礎

本文作者:leal

授權許可:

<a target="_blank" href="http://creativecommons.org/licenses/by-sa/2.0/">創作共享協定</a>

<a target="_blank" href="http://www.gnu.org/copyleft/fdl.html">gnu自由文檔許可證</a>

編輯人員:firehare, dbzhang800

我們可以使用任意一種文字編輯器,比如gedit、kedit、emacs、vi等來編寫shell腳本,它必須以如下行開始(必須放在檔案的第一行):

注意:最好使用“!/bin/bash”而不是“!/bin/sh”,如果使用tc shell改為tcsh,其他類似。

符号#!用來告訴系統執行該腳本的程式,本例使用/bin/sh。編輯結束并儲存後,如果要執行該腳本,必須先使其可執行:

此後在該腳本所在目錄下,輸入 ./filename 即可執行該腳本。

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#.e5.8f.98.e9.87.8f.e8.b5.8b.e5.80.bc.e5.92.8c.e5.bc.95.e7.94.a8">1 變量指派和引用</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#shell.e9.87.8c.e7.9a.84.e6.b5.81.e7.a8.8b.e6.8e.a7.e5.88.b6">2 shell裡的流程控制</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#if_.e8.af.ad_.e5.8f.a5">2.1 if 語 句</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#.26.26_.e5.92.8c_.7c.7c_.e6.93.8d.e4.bd.9c.e7.ac.a6">2.2 &amp;&amp; 和 || 操作符</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#case_.e8.af.ad.e5.8f.a5">2.3 case 語句</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#select_.e8.af.ad.e5.8f.a5">2.4 select 語句</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#while.2ffor_.e5.be.aa.e7.8e.af">2.5 while/for 循環</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#shell.e9.87.8c.e7.9a.84.e4.b8.80.e4.ba.9b.e7.89.b9.e6.ae.8a.e7.ac.a6.e5.8f.b7">3 shell裡的一些特殊符号</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#.e5.bc.95.e5.8f.b7">3.1 引号</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#help_document">4 help document</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#shell.e9.87.8c.e7.9a.84.e5.87.bd.e6.95.b0">5 shell裡的函數</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#.e5.91.bd.e4.bb.a4.e8.a1.8c.e5.8f.82.e6.95.b0">6 指令行參數</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#shell.e8.84.9a.e6.9c.ac.e7.a4.ba.e4.be.8b">7 shell腳本示例</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#.e4.b8.80.e8.88.ac.e7.bc.96.e7.a8.8b.e6.ad.a5.e9.aa.a4">7.1 一般程式設計步驟</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#.e4.ba.8c.e8.bf.9b.e5.88.b6.e5.88.b0.e5.8d.81.e8.bf.9b.e5.88.b6.e7.9a.84.e8.bd.ac.e6.8d.a2">7.2 二進制到十進制的轉換</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#.e6.96.87.e4.bb.b6.e5.be.aa.e7.8e.af.e6.8b.b7.e8.b4.9d">7.3 檔案循環拷貝</a>

<a target="_blank" href="http://wiki.ubuntu.org.cn/shell%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80#.e8.84.9a.e6.9c.ac.e8.b0.83.e8.af.95">8 腳本調試</a>

<a target="_blank"></a>

shell程式設計中,使用變量無需事先聲明,同時變量名的命名須遵循如下規則:

首個字元必須為字母(a-z,a-z)

中間不能有空格,可以使用下劃線(_)

不能使用标點符号

不能使用bash裡的關鍵字(可用help指令檢視保留關鍵字)

需要給變量指派時,可以這麼寫:

要取用一個變量的值,隻需在變量名前面加一個$ ( 注意: 給變量指派的時候,不能在"="兩邊留白格 )

挑個自己喜歡的編輯器,輸入上述内容,并儲存為檔案first,然後執行 chmod +x first 使其可執行,最後輸入 ./first 執行該腳本。其輸出結果如下: 

有時候變量名可能會和其它文字混淆,比如:

上述腳本并不會輸出"this is the 2nd"而是"this is the ";這是由于shell會去搜尋變量numnd的值,而實際上這個變量此時并沒有值。這時,我們可以用花括号來告訴shell要列印的是num變量:

其輸出結果為:this is the 2nd

注意花括号的位置:

其輸出結果為:this is the {2}nd

需要注意shell的預設指派是字元串指派。比如:

列印出來的不是2而是1+1。為了達到我們想要的效果有以下幾種表達方式:

注意:前兩種方式在bash下有效,在sh下會出錯。

let表示數學運算,expr用于整數值運算,每一項用空格隔開,$[]将中括号内的表達式作為數學運算先計算結果再輸出。

shell腳本中有許多變量是系統自動設定的,我們将在用到這些變量時再作說明。除了隻在腳本内有效的普通shell變量外,還有環境變量,即那些由export關鍵字處理過的變量。本文不讨論環境變量,因為它們一般隻在登入腳本中用到。

"if"表達式如果條件為真,則執行then後的部分:

大多數情況下,可以使用測試指令來對條件進行測試,比如可以比較字元串、判斷檔案是否存在及是否可讀等等……通常用" [ ] "來表示條件測試,注意這裡的空格很重要,要確定方括号前後的空格。

<dl></dl>

<dt>[ -f "somefile" ] :判斷是否是一個檔案 </dt>

<dt>[ -x "/bin/ls" ] :判斷/bin/ls是否存在并有可執行權限 </dt>

<dt>[ -n "$var" ] :判斷$var變量是否有值 </dt>

<dt>[ "$a" = "$b" ] :判斷$a和$b是否相等 </dt>

執行man test可以檢視所有測試表達式可以比較和判斷的類型。下面是一個簡單的if語句:

變量$shell包含有登入shell的名稱,我們拿它和/bin/bash進行比較以判斷目前使用的shell是否為bash。

熟悉c語言的朋友可能會喜歡下面的表達式:

這裡的 &amp;&amp; 就是一個快捷操作符,如果左邊的表達式為真則執行右邊的語句,你也可以把它看作邏輯運算裡的與操作。上述腳本表示如果/etc/shadow檔案存在,則 列印“this computer uses shadow passwords”。同樣shell程式設計中還可以用或操作(||),例如:

該腳本首先判斷mailfolder是否可讀,如果可讀則列印該檔案中的"from" 一行。如果不可讀則或操作生效,列印錯誤資訊後腳本退出。需要注意的是,這裡我們必須使用如下兩個指令:

<dl><dd>-列印錯誤資訊 </dd></dl>

<dl><dd>-退出程式 </dd></dl>

我們使用花括号以匿名函數的形式将兩個指令放到一起作為一個指令使用;普通函數稍後再作說明。即使不用與和或操作符,我們也可以用if表達式完成任何事情,但是使用與或操作符會更便利很多 。

case表達式可以用來比對一個給定的字元串,而不是數字(可别和c語言裡的switch...case混淆)。

file指令可以辨識出一個給定檔案的檔案類型,如:file lf.gz,其輸出結果為:

我們利用這點寫了一個名為smartzip的腳本,該腳本可以自動解壓bzip2, gzip和zip 類型的壓縮檔案:

你可能注意到上面使用了一個特殊變量$1,該變量包含有傳遞給該腳本的第一個參數值。也就是說,當我們運作:

$1 就是字元串 articles.zip。

select表達式是bash的一種擴充應用,擅長于互動式場合。使用者可以從一組不同的值中進行選擇:

下面是一個簡單的示例:

如果 以上腳本運作出現 select :not found 将 #!/bin/sh 改為 #!/bin/bash 該腳本的運作結果如下:

在shell中,可以使用如下循環:

隻要測試表達式條件為真,則while循環将一直運作。關鍵字"break"用來跳出循環,而關鍵字”continue”則可以跳過一個循環的餘下部分,直接跳到下一次循環中。

for循環會檢視一個字元串清單(字元串用空格分隔),并将其賦給一個變量:

下面的示例會把a b c分别列印到螢幕上:

下面是一個實用的腳本showrpm,其功能是列印一些rpm包的統計資訊:

這裡出現了第二個特殊變量$*,該變量包含有輸入的所有指令行參數值。如果你運作showrpm openssh.rpm w3m.rpm webgrep.rpm,那麼 $* 就包含有 3 個字元串,即openssh.rpm, w3m.rpm和 webgrep.rpm。

在向程式傳遞任何參數之前,程式會擴充通配符和變量。這裡所謂的擴充是指程式會把通配符(比如*)替換成适當的檔案名,把變量替換成變量值。我們可以使用引号來防止這種擴充,先來看一個例子,假設在目前目錄下有兩個jpg檔案:mail.jpg和tux.jpg。

運作結果為:

引号(單引号和雙引号)可以防止通配符*的擴充:

其運作結果為:

其中單引号更嚴格一些,它可以防止任何變量擴充;而雙引号可以防止通配符擴充但允許變量擴充:

此外還有一種防止這種擴充的方法,即使用轉義字元——反斜杆:\:

輸出結果為:

當要将幾行文字傳遞給一個指令時,用help documents是一種不錯的方法。對每個腳本寫一段幫助性的文字是很有用的,此時如果使用help documents就不必用echo函數一行行輸出。help document以 &lt;&lt; 開頭,後面接上一個字元串,這個字元串還必須出現在help document的末尾。下面是一個例子,在該例子中,我們對多個檔案進行重命名,并且使用help documents列印幫助:

這個示例有點複雜,我們需要多花點時間來說明一番。第一個if表達式判斷輸入指令行參數是否小于3個 (特殊變量$# 表示包含參數的個數) 。如果輸入參數小于3個,則将幫助文字傳遞給cat指令,然後由cat指令将其列印在螢幕上。列印幫助文字後程式退出。如果輸入參數等于或大于3個,我們 就将第一個參數指派給變量old,第二個參數指派給變量new。下一步,我們使用shift指令将第一個和第二個參數從參數清單中删除,這樣原來的第三個 參數就成為參數清單$*的第一個參數。然後我們開始循環,指令行參數清單被一個接一個地被指派給變量$file。接着我們判斷該檔案是否存在,如果存在則

通過sed指令搜尋和替換來産生新的檔案名。然後将反短斜線内指令結果指派給newfile。這樣我們就達到了目的:得到了舊檔案名和新檔案名。然後使用 mv指令進行重命名

如果你寫過比較複雜的腳本,就會發現可能在幾個地方使用了相同的代碼,這時如果用上函數,會友善很多。函數的大緻樣子如下:

你需要在每個腳本的開始對函數進行聲明。

下面是一個名為xtitlebar的腳本,它可以改變終端視窗的名稱。這裡使用了一個名為help的函數,該函數在腳本中使用了兩次:

在腳本中提供幫助是一種很好的程式設計習慣,可以友善其他使用者(和自己)使用和了解腳本。

我們已經見過$* 和 $1, $2 ... $9 等特殊變量,這些特殊變量包含了使用者從指令行輸入的參數。迄今為止,我們僅僅了解了一些簡單的指令行文法(比如一些強制性的參數和檢視幫助的-h選項)。 但是在編寫更複雜的程式時,您可能會發現您需要更多的自定義的選項。通常的慣例是在所有可選的參數之前加一個減号,後面再加上參數值 (比如檔案名)。

有好多方法可以實作對輸入參數的分析,但是下面的使用case表達式的例子無疑是一個不錯的方法。

你可以這樣運作該腳本:

傳回結果如下:

這個腳本是如何工作的呢?腳本首先在所有輸入指令行參數中進行循環,将輸入參數與case表達式進行比較,如果比對則設定一個變量并且移除該參數。根據unix系統的慣例,首先輸入的應該是包含減号的參數。

現在我們來讨論編寫一個腳本的一般步驟。任何優秀的腳本都應該具有幫助和輸入參數。寫一個架構腳本(framework.sh),該腳本包含了大多數腳本需要的架構結構,是一個非常不錯的主意。這樣一來,當我們開始編寫新腳本時,可以先執行如下指令:

然後再插入自己的函數。

讓我們來看看如下兩個示例。

腳本 b2d 将二進制數 (比如 1101) 轉換為相應的十進制數。這也是一個用expr指令進行數學運算的例子:

該腳本使用的算法是利用十進制和二進制數權值 (1,2,4,8,16,..),比如二進制"10"可以這樣轉換成十進制:

為了得到單個的二進制數我們是用了lastchar 函數。該函數使用wc –c計算字元個數,然後使用cut指令取出末尾一個字元。chop函數的功能則是移除最後一個字元。

你可能有這樣的需求并一直都這麼做:将所有發出郵件儲存到一個檔案中。但是過了幾個月之後,這個檔案可能會變得很大以至于該檔案的通路速度變慢;下 面的腳本 rotatefile 可以解決這個問題。這個腳本可以重命名郵件儲存檔案(假設為outmail)為outmail.1,而原來的outmail.1就變成了 outmail.2 等等...

help

這個腳本是如何工作的呢?在檢測到使用者提供了一個檔案名之後,首先進行一個9到1的循環;檔案名.9重命名為檔案名.10,檔案名.8重命名為檔案 名. 9……等等。循環結束之後,把原始檔案命名為檔案名.1,同時建立一個和原始檔案同名的空檔案(touch $filen)

最簡單的調試方法當然是使用echo指令。你可以在任何懷疑出錯的地方用echo列印變量值,這也是大部分shell程式員花費80%的時間用于調試的原因。shell腳本的好處在于無需重新編譯,而插入一個echo指令也不需要多少時間。

shell也有一個真正的調試模式,如果腳本"strangescript"出錯,可以使用如下指令進行調試:

7 上述指令會執行該腳本,同時顯示所有變量的值。

shell還有一個不執行腳本隻檢查文法的模式,指令如下:

這個指令會傳回所有文法錯誤。

我們希望你現在已經可以開始編寫自己的shell腳本了,盡情享受這份樂趣吧! :)

繼續閱讀