1. 程式人生 > >Linux Bash指令碼程式語言中的美學與哲學

Linux Bash指令碼程式語言中的美學與哲學

我承認,我再一次地當了標題黨。但是不可否認,這一定是一篇精華隨筆。在這一篇中,我將探討Bash指令碼語言中的美學與哲學。 這不是一篇Bash指令碼程式設計的教程,但是卻能讓人更加深入地瞭解Bash指令碼程式設計,更加快速地學習Bash指令碼程式設計。 閱讀這篇隨筆,不需要你有Bash程式設計的經驗,但一定要和我一樣熱衷於探索各種程式語言的本質,感悟它們的魅力。

其實早就想寫關於Bash的東西了。 我們平時喜歡對程式語言進行分類,比如面向過程的程式語言、面向物件的程式語言、函數語言程式設計語言等等。在我心中,我認為Bash就是一個面向字串的程式設計 語言。Bash指令碼語言的本質:一切皆是字串。 Bash指令碼語言的一切哲學都圍繞著字串:它們從哪裡來?到哪裡去?使命是什麼? Bash指令碼語言的一切美學都源自字串: 由鍵盤上幾乎所有的符號 “ $

 ~ ! # & ( ) [ ] { } | > < - . , ; * @ ' " ` \ ^” 排列組合而成的極富視覺衝擊力的、功能極其複雜的字串。

一、一切皆是字串

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*}、 $ {[email protected]}、 $ {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個等號會被當成賦值運算子。

下面圖片為以上觀點給出證據: