做者:Daniel Robbins 來自:IBM DW中國 html
咱們先看一下處理命令行自變量的簡單技巧,而後再看看 bash基本編程結構。linux
在 介紹性文章 中的樣本程序中,咱們使用環境變量 "$1"來引用第一個命令行自變量。相似地,可使用 "$2"、"$3"等來引用傳遞給腳本的第二和第三個自變量。這裏有一個例子:shell
#!/usr/bin/env bash echo name of script is $0 echo first argument is $1 echo second argument is $2 echo seventeenth argument is $17 echo number of arguments is $#
除如下兩個細節以外,此例無需說明。第一,"$0"將擴展成從命令行調用的腳本名稱,"$#"將擴展成傳遞給腳本的自變量數目。試驗以上腳本,經過傳遞不一樣類型的命令行自變量來了解其工做原理。編程
有時須要一次引用 全部 命令行自變量。針對這種用途,bash實現了變量 "$@",它擴展成全部用空格分開的命令行參數。在本文稍後的"for" 循環部分中,您將看到使用該變量的例子。bash
若是您曾用過如 C、Pascal、Python 或 Perl那樣的過程語言編程,則必定熟悉 "if" 語句和 "for"循環那樣的標準編程結構。對於這些標準結構的大多數,Bash有本身的版本。在下幾節中,將介紹幾種 bash結構,並演示這些結構和您已經熟悉的其它編程語言中結構的差別。若是之前編程很少,也沒必要擔憂。我提供了足夠的信息和示例,使您能夠跟上本文的進度。編程語言
若是您曾用 C編寫過與文件相關的代碼,則應該知道:要比較特定文件是否比另外一個文件新須要大量工做。那是由於C 沒有任何內置語法來進行這種比較,必須使用兩個 stat() 調用和兩個stat 結構來進行手工比較。相反,bash內置了標準文件比較運算符,所以,肯定「/tmp/myfile是否可讀」與查看「$myvar 是否大於 4」同樣容易。函數
下表列出最經常使用的 bash比較運算符。同時還有如何正確使用每一選項的示例。示例要跟在 "if"以後。例如:學習
if [ -z "$myvar" ] then echo "myvar is not defined" fi
運算符 | 描述 | 示例 |
---|---|---|
文件比較運算符 | ||
-e filename | 若是 filename存在,則爲真 | [ -e /var/log/syslog ] |
-d filename | 若是 filename爲目錄,則爲真 | [ -d /tmp/mydir ] |
-f filename | 若是 filename爲常規文件,則爲真 | [ -f /usr/bin/grep ] |
-L filename | 若是 filename爲符號連接,則爲真 | [ -L /usr/bin/grep ] |
-r filename | 若是 filename可讀,則爲真 | [ -r /var/log/syslog ] |
-w filename | 若是 filename可寫,則爲真 | [ -w /var/mytmp.txt ] |
-x filename | 若是 filename可執行,則爲真 | [ -L /usr/bin/grep ] |
filename1-nt filename2 | 若是 filename1比 filename2新,則爲真 | [ /tmp/install/etc/services -nt /etc/services ] |
filename1-ot filename2 | 若是 filename1比 filename2舊,則爲真 | [ /boot/bzImage -ot arch/i386/boot/bzImage ] |
字符串比較運算符 (請注意引號的使用,這是防止空格擾亂代碼的好方法) | ||
-z string | 若是 string長度爲零,則爲真 | [ -z "$myvar" ] |
-n string | 若是 string長度非零,則爲真 | [ -n "$myvar" ] |
string1= string2 | 若是 string1與 string2相同,則爲真 | [ "$myvar" = "one two three" ] |
string1!= string2 | 若是 string1與 string2不一樣,則爲真 | [ "$myvar" != "one two three" ] |
算術比較運算符 | ||
num1-eq num2 | 等於 | [ 3 -eq $mynum ] |
num1-ne num2 | 不等於 | [ 3 -ne $mynum ] |
num1-lt num2 | 小於 | [ 3 -lt $mynum ] |
num1-le num2 | 小於或等於 | [ 3 -le $mynum ] |
num1-gt num2 | 大於 | [ 3 -gt $mynum ] |
num1-ge num2 | 大於或等於 | [ 3 -ge $mynum ] |
有時,有幾種不一樣方法來進行特定比較。例如,如下兩個代碼段的功能相同:命令行
if [ "$myvar" -eq 3 ] then echo "myvar equals 3" fi if [ "$myvar" = "3" ] then echo "myvar equals 3" fi
上面兩個比較執行相同的功能,可是第一個使用算術比較運算符,而第二個使用字符串比較運算符。設計
大多數時候,雖然能夠不使用括起字符串和字符串變量的雙引號,但這並非好主意。爲何呢?由於若是環境變量中恰巧有一個空格或製表鍵,bash將沒法分辨,從而沒法正常工做。這裏有一個錯誤的比較示例:
if [ $myvar = "foo bar oni" ] then echo "yes" fi
在上例中,若是 myvar 等於"foo",則代碼將按預想工做,不進行打印。可是,若是 myvar 等於 "foobar oni",則代碼將因如下錯誤失敗:
[: too many arguments
在這種狀況下,"$myvar"(等於 "foo bar oni")中的空格迷惑了bash。bash 擴展 "$myvar" 以後,代碼以下:
[ foo bar oni = "foo bar oni" ]
由於環境變量沒放在雙引號中,因此 bash認爲方括號中的自變量過多。能夠用雙引號將字符串自變量括起來消除該問題。請記住,若是養成將全部字符串自變量用雙引號括起的習慣,將除去不少相似的編程錯誤。"foobar oni" 比較 應該寫成:
if [ "$myvar" = "foo bar oni" ] then echo "yes" fi
若是要擴展環境變量,則必須將它們用 雙引號、而不是單引號括起。單引號 禁用 變量(和歷史)擴展。
以上代碼將按預想工做,而不會有任何使人不快的意外出現。
好了,已經講了條件語句,下面該探索 bash 循環結構了。咱們將從標準的"for" 循環開始。這裏有一個簡單的例子:
#!/usr/bin/env bash for x in one two three four do echo number $x done 輸出: number one number two number three number four
發生了什麼?"for" 循環中的 "for x" 部分定義了一個名爲 "$x"的新環境變量(也稱爲循環控制變量),它的值被依次設置爲"one"、"two"、"three" 和"four"。每一次賦值以後,執行一次循環體("do" 和 "done"之間的代碼)。在循環體內,象其它環境變量同樣,使用標準的變量擴展語法來引用循環控制變量"$x"。還要注意,"for" 循環老是接收 "in"語句以後的某種類型的字列表。在本例中,指定了四個英語單詞,可是字列表也能夠引用磁盤上的文件,甚至文件通配符。看看下面的例子,該例演示如何使用標準shell 通配符:
#!/usr/bin/env bash for myfile in /etc/r* do if [ -d "$myfile" ] then echo "$myfile (dir)" else echo "$myfile" fi done 輸出: /etc/rc.d (dir) /etc/resolv.conf /etc/resolv.conf~ /etc/rpc
以上代碼列出在 /etc 中每一個以 "r" 開頭的文件。要作到這點,bash在執行循環以前首先取得通配符 /etc/r*,而後擴展它,用字符串/etc/rc.d /etc/resolv.conf /etc/resolv.conf~ /etc/rpc替換。一旦進入循環,根據 myfile 是否爲目錄,"-d"條件運算符用來執行兩個不一樣操做。若是是目錄,則將 "(dir)"附加到輸出行。
還能夠在字列表中使用多個通配符、甚至是環境變量:
for x in /etc/r--? /var/lo* /home/drobbins/mystuff/* /tmp/${MYPATH}/* do cp $x /mnt/mydir done
Bash將在全部正確位置上執行通配符和環境變量擴展,並可能建立一個很是長的字列表。
雖然全部通配符擴展現例使用了 絕對路徑,但也可使用相對路徑,以下所示:
for x in ../* mystuff/* do echo $x is a silly file done
在上例中,bash相對於當前工做目錄執行通配符擴展,就象在命令行中使用相對路徑同樣。研究一下通配符擴展。您將注意到,若是在通配符中使用絕對路徑,bash將通配符擴展成一個絕對路徑列表。不然,bash將在後面的字列表中使用相對路徑。若是隻引用當前工做目錄中的文件(例如,若是輸入"for x in*"),則產生的文件列表將沒有路徑信息的前綴。請記住,可使用"basename" 可執行程序來除去前面的路徑信息,以下所示:
for x in /var/log/* do echo `basename $x` is a file living in /var/log done
固然,在腳本的命令行自變量上執行循環一般很方便。這裏有一個如何使用本文開始提到的"$@" 變量的例子:
#!/usr/bin/env bash for thing in "$@" do echo you typed ${thing}. done 輸出: $ allargs hello there you silly you typed hello. you typed there. you typed you. you typed silly.
在學習另外一類型的循環結構以前,最好先熟悉如何執行 shell算術。是的,確實如此:可使用 shell結構來執行簡單的整數運算。只需將特定的算術表達式用 "$((" 和 "))"括起,bash 就能夠計算表達式。這裏有一些例子:
$ echo $(( 100 / 3 )) 33 $ myvar="56" $ echo $(( $myvar + 12 )) 68 $ echo $(( $myvar - $myvar )) 0 $ myvar=$(( $myvar + 1 )) $ echo $myvar 57
只要特定條件爲真,"while" 語句就會執行,其格式以下:
while [ condition ] do statements done
一般使用 "While" 語句來循環必定次數,好比,下例將循環 10 次:
myvar=0 while [ $myvar -ne 10 ] do echo $myvar myvar=$(( $myvar + 1 )) done
能夠看到,上例使用了算術表達式來使條件最終爲假,並致使循環終止。
"Until" 語句提供了與 "while"語句相反的功能:只要特定條件爲 假 ,它們就重複。下面是一個與前面的"while" 循環具備同等功能的 "until" 循環:
myvar=0 until [ $myvar -eq 10 ] do echo $myvar myvar=$(( $myvar + 1 )) done
Case 語句是另外一種便利的條件結構。這裏有一個示例片斷:
case "${x##*.}" in gz) gzunpack ${SROOT}/${x} ;; bz2) bz2unpack ${SROOT}/${x} ;; *) echo "Archive format not recognized." exit ;; esac
在上例中,bash 首先擴展 "${x##*.}"。在代碼中,"$x"是文件的名稱,"${x##.*}"除去文件中最後句點後文本以外的全部文本。而後,bash 將產生的字符串與")" 左邊列出的值作比較。在本例中,"${x##.}" 先與 "gz" 比較,而後是"bz2",最後是 ""。若是 "${x##.}"與這些字符串或模式中的任何一個匹配,則執行緊接 ")" 以後的行,直到";;" 爲止,而後 bash 繼續執行結束符 "esac"以後的行。若是不匹配任何模式或字符串,則不執行任何代碼行,在這個特殊的代碼片斷中,至少要執行一個代碼塊,由於任何不與"gz" 或 "bz2" 匹配的字符串都將與 "" 模式匹配。
在 bash 中,甚至能夠定義與其它過程語言(如 Pascal 和C)相似的函數。在 bash中,函數甚至可使用與腳本接收命令行自變量相似的方式來接收自變量。讓咱們看一下樣本函數定義,而後再從那裏繼續:
tarview() { echo -n "Displaying contents of $1 " if [ ${1##*.} = tar ] then echo "(uncompressed tar)" tar tvf $1 elif [ ${1##*.} = gz ] then echo "(gzip-compressed tar)" tar tzvf $1 elif [ ${1##*.} = bz2 ] then echo "(bzip2-compressed tar)" cat $1 | bzip2 -d | tar tvf - fi }
可使用 "case" 語句來編寫上面的代碼。您知道如何編寫嗎?
咱們在上面定義了一個名爲 "tarview"的函數,它接收一個自變量,即某種類型的 tar文件。在執行該函數時,它肯定自變量是哪一種 tar文件類型(未壓縮的、gzip 壓縮的或 bzip2壓縮的),打印一行信息性消息,而後顯示 tar文件的內容。應該以下調用上面的函數(在輸入、粘貼或找到該函數後,從腳本或命令行調用它):
$ tarview shorten.tar.gz Displaying contents of shorten.tar.gz (gzip-compressed tar) drwxr-xr-x ajr/abbot 0 1999-02-27 16:17 shorten-2.3a/ -rw-r--r-- ajr/abbot 1143 1997-09-04 04:06 shorten-2.3a/Makefile -rw-r--r-- ajr/abbot 1199 1996-02-04 12:24 shorten-2.3a/INSTALL -rw-r--r-- ajr/abbot 839 1996-05-29 00:19 shorten-2.3a/LICENSE ....
別忘了,能夠將函數(如上面的函數)放在 ~/.bashrc 或 ~/.bash_profile中,以便在 bash 中隨時使用它們。
如您所見,可使用與引用命令行自變量一樣的機制來在函數定義內部引用自變量。另外,將把"$#" 宏擴展成包含自變量的數目。惟一可能不徹底相同的是變量"$0",它將擴展成字符串 "bash"(若是從 shell交互運行函數)或調用函數的腳本名稱。
常常須要在函數中建立環境變量。雖然有可能,可是還有一個技術細節應該瞭解。在大多數編譯語言(如C)中,當在函數內部建立變量時,變量被放置在單獨的局部名稱空間中。所以,若是在C 中定義一個名爲 myfunction 的函數,並在該函數中定義一個名爲 "x"的自變量,則任何名爲 "x"的全局變量(函數以外的變量)將不受它的印象,從而消除了負做用。
在 C 中是這樣,但在 bash 中卻不是。在 bash中,每當在函數內部建立環境變量,就將其添加到 全局名稱空間。這意味着,該變量將重寫函數以外的全局變量,並在函數退出以後繼續存在:
#!/usr/bin/env bash myvar="hello" myfunc() { myvar="one two three" for x in $myvar do echo $x done } myfunc echo $myvar $x
運行此腳本時,它將輸出 "one two threethree",這顯示了在函數中定義的 "$myvar" 如何影響全局變量"$myvar",以及循環控制變量 "$x" 如何在函數退出以後繼續存在(若是"$x" 全局變量存在,也將受到影響)。
在這個簡單的例子中,很容易找到該錯誤,並經過使用其它變量名來改正錯誤。但這不是正確的方法,解決此問題的最好方法是經過使用"local" 命令,在一開始就預防影響全局變量的可能性。當使用 "local"在函數內部建立變量時,將把它們放在 局部名稱空間中,而且不會影響任何全局變量。這裏演示瞭如何實現上述代碼,以便不重寫全局變量:
#!/usr/bin/env bash myvar="hello" myfunc() { local x local myvar="one two three" for x in $myvar do echo $x done } myfunc echo $myvar $x
此函數將輸出 "hello" -- 不重寫全局變量 "$myvar","$x" 在 myfunc以外不繼續存在。在函數的第一行,咱們建立了之後要使用的局部變量x,而在第二個例子 (local myvar="one two three"")中,咱們建立了局部變量myvar, 同時 爲其賦值。在將循環控制變量定義爲局部變量時,使用第一種形式很方便,由於不容許說:"forlocal x in$myvar"。此函數不影響任何全局變量,鼓勵您用這種方式設計全部的函數。只有在明確但願要修改全局變量時,才 不應該使用 "local"。
咱們已經學習了最基本的 bash 功能,如今要看一下如何基於 bash開發整個應用程序。下一部分正要講到。再見!
參考資料