Tuesday, September 26, 2017

awk style 的 shell script

有一段時間我很喜歡寫一種特定形式的 shell script ,我自己稱之為 awk style shell script 。它通常是長成這個樣子:

這種風格的程式碼,我之前寫得很爽,因為覺得寫起來很快,字數、行數都少。後來回頭看的時候,雖然覺得有些優點、也有些缺點,卻也不知其所以然,最近終於想出原因了。

這種風格它有幾項特色:
1.  主要使用 awk
2.  awk 指令用 unix pipe 串接

仔細分析這種風格使用的程式語言的概念:
1. 每一行的 awk 指令,可以視為是函數呼叫,而且是「高階函數」(higher order function)  。是高階函數是因為 awk 指令都隱含了一個迴圈、每一行就是對應一次的迴圈操作。
2. 每一行的 awk 指令裡的部分運算內容,可以視為是傳遞進入高階函數裡的運算,換言之,它們可以視為是匿名函數
3. unix pipe 之間,是通過有特定格式「字串流」 (string stream) 來傳遞資料。 其『特定格式』是表格的形式,以斷行字元 (\n) 來分隔 row,以空白字元 (\s) 來分隔 column

有了上述的分析,要講出優點就容易多了:
1. 因為使用了高階函數的概念,要寫的 boilerplate code 就變少了,寫起來自然快。
2. 因為使用了匿名函數,一方面減少了 boilerplate code ,還省去了函數命名的心理負擔。
3. 使用有特定格式的字串流來傳遞資料,因為特定格式通常可以描述輸出所需要的完整資料,程式就可以變成「單向的資料流」。程式要復用的時候,通常就是截取資料流之中,可以復用的部分來復用。
此外,這種使用字串流的風格與 C style 的程式設計最大的差異點在於: 「C style 的程式設計風格是只要是不會被函數修改的資料,都盡量不要放進函數的輸入、輸出。」這是因為執行效能考量,減少不必要的拷貝。付出的代價則是,往往為了輸入輸出引數的一點點變化,要寫許多重覆的程式、或是套接 (adapter) 的程式碼,因而減少了開發效率。

4. Unix pipe 雖然不是惰性求值 (lazy evaluation) ,而是 buffered queue ,但是使用 unix pipe 寫出的 shell script 卻是惰性求值的風格:『不需要管資料的長度』。

另一方面,也有缺點,但是要克服缺點,就需要使用 python/clojure 之類的語言才容易克服了。
1. 每一行 (row) 的字串資料對應的 awk 指令,其實就是 functional programming 裡的 map/filter 操作。改成用 map/filter/reduce 來寫,表現力不會打折扣,可讀性還會提高。
2. 匿名函數如果要復用時,還是給它取個名稱比較好。
3. 使用特定格式的字串流來傳遞資料,不如使用 list 或是 hash map 來傳遞資料。後者的表現能力更強、而且還可以夾帶型別資訊