我認可,我再一次地當了標題黨。可是不能否認,這必定是一篇精華隨筆。在這一篇中,我將探討Bash腳本語言中的美學與哲學。 這不是一篇Bash腳本編程的教程,可是卻能讓人更加深刻地瞭解Bash腳本編程,更加快速地學習Bash腳本編程。 閱讀這篇隨筆,不須要你有Bash編程的經驗,但必定要和我同樣熱衷於探索各類編程語言的本質,感悟它們的魅力。html
其實早就想寫關於Bash的東西了。 咱們平時喜歡對編程語言進行分類,好比面向過程的編程語言、面向對象的編程語言、函數式編程語言等等。在我心中,我認爲Bash就是一個面向字符串的編程 語言。Bash腳本語言的本質:一切皆是字符串。 Bash腳本語言的一切哲學都圍繞着字符串:它們從哪裏來?到哪裏去?使命是什麼? Bash腳本語言的一切美學都源自字符串: 由鍵盤上幾乎全部的符號 「 $ ~ ! # & ( ) [ ] { } | > < - . , ; * @ ' " ` \ ^」 排列組合而成的極富視覺衝擊力的、功能極其複雜的字符串。linux
Linux Bash Shell入門教程 http://www.linuxidc.com/Linux/2013-08/8848.htm正則表達式
Bash是一個Shell,Shell出現的初衷是爲了將系統中的各類工具粘合在一塊兒,因此它最根本的功能是調用各類命令。 可是Bash又提供了豐富的編程功能。 咱們常常對編程語言進行分類,好比面向過程的語言、面向對象的語言、面向函數的語言等等。 能夠把Bash腳本語言當作是一個面向字符串的語言。 Bash語言的本質就是:一切都是字符串。 看看下圖中的這些變量:編程
上圖是我在交互式的Bash命令行中作的一些演示。在上圖中,我對變量分別賦值,無論等號右邊是一個沒有引號的字符串,仍是帶有引號的字符串, 甚至數字,或者數學表達式,最終的結果,變量裏面存儲的都是字符串。我使用一個for循環顯示全部的變量,能夠看到數學表達式也只是以字符串的形式儲存, 沒有被求值。數組
若是一切都是沒有特殊功能的平凡的字符串,那就沒法構成一門編程語言。在Bash中,有不少符號具備特殊含義,好比「 $ 」符號被用於字符串展開,「&」符號用於讓命令在後臺執行, 「|」用做管道, 「>」 「<」用於輸入輸出重定向等等。因此在Bash中,雖然一樣是字符串,可是被引號包圍的字符串和不被引號包圍的字符串使用起來是不同的,被單引號 包圍的字符串和被雙引號包圍起來的字符串也是不同的。bash
究竟帶引號的字符串和不帶引號的字符串使用起來有什麼不同呢?下圖是我構建的一些比較典型的例子:編程語言
在上圖中,我展現了Bash中生成字符串的7種方法:大括號展開、波浪符展開、參數展開、命令替換、算術展開、單詞分割和文件路徑展開。還有兩 種生成字符串的方式沒有講(Process substitution和歷史命令展開)。在使用Bash腳本編程的時候,瞭解以上7種字符串生成的方式就夠了。在交互式使用Bash命令行的時候,還 須要瞭解歷史命令展開,熟練使用歷史命令展開可讓人事半功倍。ide
在上面的圖片中能夠看到,有一些展開方式在被雙引號包圍的字符串中是不起做用的,好比大括號展開、波浪符展開、單詞分割、文件路徑展開,而只有參數展開、命令替換和算術展開是起做用的。從圖片中還能夠看出,字符串中的參數展開、命令替換和算術展開都是由「 $ 」符號引導,命令替換還能夠由「`」引導。因此,能夠進一步總結爲,在雙引號包圍的字符串中,只有「 $ 、`、\」這三個字符具備特殊含義。函數式編程
若是想讓任何一個字符都不具備特殊含義,可使用單引號將字符串包圍。好比使用正則表達式的時候,還好比使用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字符串展開中最複雜的,其中包括用戶定義的變量,包括全部的環境變量,以上兩種展開方式都是「 $ 」後跟變量名,還包括位置變量「 $ 一、 $ 二、 ...、 $ 九、 ... 」,其它特殊變量:「 $ @、 $ *、 $ #、 $ -、 $ !、 $ 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個等號會被當成賦值運算符。
下面圖片爲以上觀點給出證據:
本文來自:Linux學習網