我承認,我再一次地當了標題黨。但是不可否認,這一定是一篇精華隨筆。在這一篇中,我將探討Bash腳本語言中的美學與哲學。 這不是一篇Bash腳本編程的教程,但是卻能讓人更加深入地了解Bash腳本編程,更加快速地學習Bash腳本編程。 閱讀這篇隨筆,不需要你有Bash編程的經驗,但一定要和我一樣熱衷於探索各種編程語言的本質,感悟它們的魅力。
其實早就想寫關於Bash的東西了。 我們平時喜歡對編程語言進行分類,比如面向過程的編程語言、面向對象的編程語言、函數式編程語言等等。在我心中,我認為Bash就是一個面向字符串的編程語言。Bash腳本語言的本質:一切皆是字符串。 Bash腳本語言的一切哲學都圍繞著字符串:它們從哪裡來?到哪裡去?使命是什麼? Bash腳本語言的一切美學都源自字符串: 由鍵盤上幾乎所有的符號 “$ ~ ! # & ( ) [ ] { } | > < - . , ; * @ ' " ` \ ^” 排列組合而成的極富視覺沖擊力的、功能極其復雜的字符串。
Linux Bash Shell入門教程 http://www.linuxidc.com/Linux/2013-08/8848.htm
一、一切皆是字符串
Bash是一個Shell,Shell出現的初衷是為了將系統中的各種工具粘合在一起,所以它最根本的功能是調用各種命令。 但是Bash又提供了豐富的編程功能。 我們經常對編程語言進行分類,比如面向過程的語言、面向對象的語言、面向函數的語言等等。 可以把Bash腳本語言看成是一個面向字符串的語言。 Bash語言的本質就是:一切都是字符串。 看看下圖中的這些變量:
上圖是我在交互式的Bash命令行中做的一些演示。在上圖中,我對變量分別賦值,不管等號右邊是一個沒有引號的字符串,還是帶有引號的字符串,甚至數字,或者數學表達式,最終的結果,變量裡面存儲的都是字符串。我使用一個for循環顯示所有的變量,可以看到數學表達式也只是以字符串的形式儲存,沒有被求值。
二、引用和元字符
如果一切都是沒有特殊功能的平凡的字符串,那就無法構成一門編程語言。在Bash中,有很多符號具有特殊含義,比如“$”符號被用於字符串展開,“&”符號用於讓命令在後台執行, “|”用作管道, “>” “<”用於輸入輸出重定向等等。所以在Bash中,雖然同樣是字符串,但是被引號包圍的字符串和不被引號包圍的字符串使用起來是不一樣的,被單引號包圍的字符串和被雙引號包圍起來的字符串也是不一樣的。
究竟帶引號的字符串和不帶引號的字符串使用起來有什麼不一樣呢?下圖是我構建的一些比較典型的例子:
在上圖中,我展示了Bash中生成字符串的7種方法:大括號展開、波浪符展開、參數展開、命令替換、算術展開、單詞分割和文件路徑展開。還有兩種生成字符串的方式沒有講(Process substitution和歷史命令展開)。在使用Bash腳本編程的時候,了解以上7種字符串生成的方式就夠了。在交互式使用Bash命令行的時候,還需要了解歷史命令展開,熟練使用歷史命令展開可以讓人事半功倍。
在上面的圖片中可以看到,有一些展開方式在被雙引號包圍的字符串中是不起作用的,比如大括號展開、波浪符展開、單詞分割、文件路徑展開,而只有參數展開、命令替換和算術展開是起作用的。從圖片中還可以看出,字符串中的參數展開、命令替換和算術展開都是由“$”符號引導,命令替換還可以由“`”引導。所以,可以進一步總結為,在雙引號包圍的字符串中,只有“$、`、\”這三個字符具有特殊含義。
如果想讓任何一個字符都不具有特殊含義,可以使用單引號將字符串包圍。比如使用正則表達式的時候,還比如使用sed、awk等工具的時候,由於sed和awk自己執行的命令中往往包含有很多特殊字符,所以它們的命令最好用單引號包圍。 比如使用awk命令顯示/etc/passwd文件中的每個用戶的用戶名和全名,可以使用這個命令 awk -e '{print $1,$5}' ,其中,傳遞給awk的命令用單引號包圍,說明bash不執行其中的任何替換或展開。
另外一個特殊的字符是“\”,它也是引用的一種。它可以解除緊跟在它後面的一個特殊字符的特殊含義(引用)。之所以需要“\”的存在,是因為在Bash中,有些字符稱為元字符,這些字符一旦出現,就會將一個字符串分割為多個子串。如果需要在一個字符串中包含這些元字符本身,就必須對它們進行引用。如下圖:
最常見的元字符就是空格。 從上面幾張圖片可以看出,如果要將一個含有空格的字符串賦值給一個變量,要麼把這個字符串用雙引號包圍,要麼使用“\”對空格進行引用。 從上圖中可以看出,Bash中只有9個元字符,它們分別是“| & ( ) ; < > space tab”,而在其它編程語言中經常出現的元字符“. { } [ ]”以及作為數學運算的加減乘除,在Bash中都不是元字符。
三、字符串從哪裡來、到哪裡去
介紹完字符串、介紹完引用和元字符,下一個目標就是來探討這一個哲學問題:字符串從哪裡來、到哪裡去?通過該哲學問題的探討,可以推導出Bash腳本語言的整個語法。字符串從哪裡來?很顯然,其中一個很直接的來源就是我們從鍵盤上敲上去的。除此之外,就是我前面提到的七八九種字符串展開的方法了。
字符串展開的流程如下:
1.先用元字符將一個字符串分割為多個子串;
2.如果字符串是用來給變量賦值,則不管它是否被雙引號包圍,都認為它被雙引號包圍;
3.如果字符串不被單引號和雙引號包圍,則進行大括號展開,即將{a,b}c展開為ab ac;
以上三個流程可以通過下圖證明:
4.如果字符串不被單引號或雙引號包圍,則進行波浪符展開,即將~/展開為用戶的主目錄,將~+/展開為當前工作目錄(PWD),將~-/展開為上一個工作目錄(OLDPWD);
5.如果字符串不被單引號包圍,則進行參數和變量展開;這一類的展開全都以“$”開頭,這是整個Bash字符串展開中最復雜的,其中包括用戶定義的變量,包括所有的環境變量,以上兩種展開方式都是“$”後跟變量名,還包括位置變量“$1、 $2、 ...、 $9、 ... ”,其它特殊變量:“$@、 $*、 $#、 $-、 $!、 $0、 $?、 $_ ”,甚至還有數組:“${var[i]}”, 還可以在展開的過程中對字符串進行各種復雜的操作,如:“ ${parameter:-word}、 ${parameter:=word}、 ${parameter:+word}、 ;${parameter:?word}、 ${parameter:offset}、 ${parameter:offset:length}、 ${!prefix*}、 ${!prefix@}、 ${name[@]}、 ${!name[*]}、 ${#parameter}、 ${parameter#word}、 ${parameter##word}、 ${parameter%word}、 ${parameter%%word}、 ${parameter/pattern/string}、 ${parameter^pattern}、 ${parameter^^pattern}、 ${parameter,pattern}、 ${parameter,,pattern}”;
6.如果字符串不被單引號包圍,則進行命令替換;命令替換有兩種格式,一種是$(...),一種是`...`;也就是將命令的輸出作為字符串的內容;
7.如果字符串不被單引號包圍,則進行算術展開;算術展開的格式為$((...));
8.如果字符串不被單引號或雙引號包圍,則進行單詞分割;
9.如果字符串不被單引號或雙引號包圍,則進行文件路徑展開;
10.以上流程全部完成後,最後去掉字符串外面的引號(如果有的話)。以上流程只按以上順序進行一遍。比如不會在變量展開後再進行大括號展開,更不會在第10步去除引用後執行前面的任何一步。如果需要將流程再走一遍,請使用eval。
探討完了字符串從哪裡來,下面來看看字符串到哪裡去。也就是怎麼使用這些字符串。使用字符串有以下幾種方式:
1.把它當命令執行;這是Bash中的最根本的用法,畢竟Shell的存在就是為了粘合各種命令。如果一個字符串出現在本該命令出現的地方(比如一行的開頭,或者關鍵字then、do等的後面),它將會被當成命令執行,如果它不是個合法的命令,就會報錯;
2.把它當成表達式;Bash中本沒有表達式,但是有了((...))和[[...]],就有了表達式;((...))可以把它裡面的字符串當成算術表達式,而[[...]]會把它裡面的字符串當邏輯表達式,僅此兩個特例;
3.給變量賦值;這也是一個特例,有點破壞Bash編程語言語法哲學的完整性。為什麼這麼說呢?因為“=”即不是一個元字符,也不允許兩邊有空格,而且只有第1個等號會被當成賦值運算符。
下面圖片為以上觀點給出證據:
更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2015-03/114436p2.htm