通常情況下,我們從命令行輸入命令每輸入一次就能夠得到系統的一次響應。一旦需要我們一個接著一個的輸入命令而最後才得到結果的時候,這樣的做法顯然就沒有效率。要達到這樣的目的,通常我們利用shell程序或者shell腳本來實現。 一、簡介 Shell編程有很多類似C語言和其他程序語言的特徵,但是又沒有編程語言那樣複雜。Shell程序就是放在一個文件中的一系列Linux命令和實用程序,在執行的時候,通過Linux一個接著一個地解釋和執行每個命令。 下面我們來看一個簡單的shell程序: 1、首先建立一個內容如下的文件,名字為date,將其存放在目錄下的bin子目錄中。 #Program date #usageto ::show the date in this way (註釋) echo 「Mr.$USER,Today is:」 echo date 「+%B%d%A」 echo 「Whish you a lucky day !」 2、編輯完該文件之後它還不能執行,我們需要給它設置可執行許可權。使用如下命令: chmod +x date 通過以上過程之後,我們就可以像使用ls命令一樣執行這個shell程序. [beichen@localhost bin]$ date Mr.beichen,Today is: January 13 Friday Whish you a lucky day ! 為了在任何目錄里都可以執行這個程序,可以將bin的這個目錄添加到路徑中去。 [beichen@localhost bin]$ PATH=$PATH:$HOME/bin (註:這裡的$HOME代替的是/home/beichen,而bin目錄是自己建的一個目錄) 另外一種執行date的方法就是把它作為一個參數傳給shell命令: [beichen@localhost /]$ bash date Mr.beichen,Today is: January 13 Friday Whish you a lucky day ! 儘管在前面我們使用chmod +x date將date設置為可執行,其實不設置也沒有關係,但在Linux里執行它,需要先告訴系統它是一個可執行的腳本。 [beichen@localhost /]$ .date Mr.beichen,Today is: January 13 Friday Whish you a lucky day ! 即在date前面加上一個點」.」,並且用空格與後面的shell腳本的文件名隔開。當然,不推薦這樣做。 二、shell參數 如同ls 命令可以接受目錄等作為它的參數一樣,在shell編程時同樣可以使用參數。Shell有位置參數和內部參數。 1、 位置參數 由系統提供的參數稱為位置參數。位置參數的值可以用$N得到,N是一個數字,如果為1,即$1.類似C語言中的數組,Linux會把輸入的命令字元串分段並給每段進行標號,標號從0開始。第0號為程序名字,從1開始就表示傳遞給程序的參數。如$0表示程序的名字,$1表示傳遞給程序的第一個參數,以此類推。 2、 內部參數 上述過程中的$0是一個內部變數,它是必須的,而$1則可有可無。和$0一樣的內部變數還有以下幾個。 $# ----傳遞給程序的總的參數數目 $? ----上一個代碼或者shell程序在shell中退出的情況,如果正常退出則返回0,反之為非0值。 $* ----傳遞給程序的所有參數組成的字元串。 下面舉例進行說明: 建立一個內容為如下的程序P1: echo 「Program name is $0」 echo 「There are totally $# parameters passed to this program」 echo 「The last is $?」 echo 「The parameters are $*」 執行后的結果如下: [beichen@localhost bin]$ P1 this is a test program //傳遞5個參數 Program name is /home/beichen/bin/P1 //給出程序的完整路徑和名字 There are totally 5 parameters passed to this program //參數的總數 The last is 0 //程序執行結果 The parameters are this is a test program //返回有參數組成的字元串 有時,有幾種不同方法來進行特定比較。例如,以下兩個代碼段的功能相同: 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 等於 "foo bar oni",則代碼將因以下錯誤失敗: [: too many arguments 在這種情況下,"$myvar"(等於 "foo bar oni")中的空格迷惑了 bash。bash 擴展 "$myvar" 之後,代碼如下: [ foo bar oni = "foo bar oni" ] 因為環境變數沒放在雙引號中,所以 bash 認為方括弧中的自變數過多。可以用雙引號將字元串自變數括起來消除該問題。請記住,如果養成將所有字元串自變數用雙引號括起的習慣,將除去很多類似的編程錯誤。"foo bar oni" 比較 應該寫成: if [ "$myvar" = "foo bar oni" ] then echo "yes" fi 更多引用細節 如果要擴展環境變數,則必須將它們用 雙引號、而不是單引號括起。單引號 禁用 變數(和歷史)擴展。 以上代碼將按預想工作,而不會有任何令人不快的意外出現。 循環結構:"for" 好了,已經講了條件語句,下面該探索 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 算術。是的,確實如此:可以使用 shell 結構來執行簡單的整數運算。只需將特定的算術表達式用 "$((" 和 "))" 括起,bash 就可以計算表達式。這裡有一些例子: $ echo $(( 100 / 3 )) 33 $ myvar="56" $ echo $(( $myvar + 12 )) 68 $ echo $(( $myvar - $myvar )) 0 $ myvar=$(( $myvar + 1 )) $ echo $myvar 57 更多的循環結構:"while" 和 "until" 只要特定條件為真,"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 語句是另一種便利的條件結構。這裡有一個示例片段: 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 three three",這顯示了在函數中定義的 "$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, 同時 為其賦值。在將循環控制變數定義為局部變數時,使用第一種形式很方便,因為不允許說:"for local x in $myvar"。此函數不影響任何全局變數,鼓勵您用這種方式設計所有的函數。只有在明確希望要修改全局變數時,才 不應該使用 "local"。
[火星人
]
linux bash shell編程基礎 已經有530 次圍觀
本文地址: http://coctec.com/docs/linux/show-post-184378.html