我們已經看過如何撰寫簡單的指令,也就是指令後面接著引數。 現在我們要來看如何組合簡單的指令,做出更複雜的指令。
在開始之前,先快速介紹一下輸入與輸出。
行程有「標準 I/O 通道」。
tr指令就是一個很純粹的例子。
它所做的只是從輸入讀取文字、進行字元轉換,然後把轉換後的文字印到輸出。
我們之後會在課程大綱裡看到更多關於操作「stdio」通道的內容。
這是 shell 程式設計的「殺手級功能」之一。 管線讓你能對文字串流做出精巧的轉換。
要產生一份排序後的使用者清單:
cat /etc/passwd | cut -d : -f 1 | sort
管線符號(|)會把一個指令的輸出連接到另一個指令的輸入。
cut會讀取cat的輸出,而sort會讀取cut的輸出。
預設情況下,管線中的每個指令都會在各自獨立的子 shell 中執行。 (子 shell 是一個子行程,也就是目前執行中 shell 的複本。)
執行管線是有效能成本的。
如果你發現自己寫出由相似指令組成的長管線,不妨考慮把它們合併成單一指令。
舉例來說,用到多個grep、cut、sed、awk和tr的管線,通常可以合併成單一個awk指令來提升效率。
管線的結束狀態,就是管線中最後一個指令的結束狀態。 這表示即使前面有指令失敗了,只要最後一個指令成功,整條管線的結束狀態就是 0。
有一個 shell 設定可以改變這點;
如果啟用了「pipefail」設定(使用set -o pipefail),管線的結束狀態就會是:
指令清單是一連串的管線,以;(或換行)、&&或||分隔。
A; B是一種指令清單,其中B會在A完成之後執行。A && B,其中B只在A成功(以狀態 0 結束)時執行。A || B,其中B只在A失敗(以不為 0 的狀態結束)時執行。指令清單的結束狀態,就是最後被執行的那個指令的結束狀態。
&&和||運算子可以串接起來,讓下一個指令根據前面指令的狀態有條件地執行。
例如
A && B && C && D || E
請謹慎使用這些邏輯運算子。 它們很快就會導致程式碼難以閱讀,或是邏輯難以理解。
舉例來說,你可能會以為下面這兩個是一樣的:
if A; then B; else C; fi
A && B || C
差別在於:C 什麼時候會執行?
if敘述)中,唯有 A 失敗時,C 才會執行。這裡有幾個例子,說明指令清單如何簡化程式碼。
假設你有一個包含三角形資料的資料檔, 而每個三角形用三行分別存放它的三個邊。
$ cat triangle.dat
3
4
5
9
12
14
你可以用一個while迴圈,並把三個分開的 read 指令當作它的條件:
while read a && read b && read c; do
if is_pythagorean "$a" "$b" "$c"; then
echo "$a:$b:$c is pythagorean"
else
echo "$a:$b:$c is not pythagorean"
fi
done < triangle.dat
假設is_pythagorean是一個判斷三邊是否符合畢氏定理的指令,輸出會是:
3:4:5 is pythagorean
9:12:14 is not pythagorean
許多程式語言都有某種形式的斷言,當某個條件不成立時會拋出例外
assert(user == "Administrator", "wrong user");
我們可以用 Bash 的 OR 運算子來模擬這個功能:
die () { # a little convenience function
echo "$*" >&2
exit 1
}
[[ $user == "Administrator" ]] || die "wrong user"
[[ $password == "secret!" ]] || die "incorrect password"
冗長的指令清單很快就會變得難以閱讀。 多加使用換行可以幫上很大的忙。
看看這個例子:當兩個條件都成立時,就把某個單字加入陣列。
[[ "$word" != "$topic" ]] && [[ "$key" == "$(sorted "$topic")" ]] && anagrams+=("$candidate")
Bash 允許你在管線符號或邏輯運算子後面加上換行。
[[ "$word" != "$topic" ]] &&
[[ "$key" == "$(sorted "$topic")" ]] &&
anagrams+=("$candidate")
不過,運算子放在行尾時很容易被忽略。 使用_續行_可以讓你把運算子放在最前面,也就更容易看出這個清單還會繼續:
[[ "$word" != "$topic" ]] \
&& [[ "$key" == "$(sorted "$topic")" ]] \
&& anagrams+=("$candidate")
_續行_就是「反斜線」和「換行」這兩個字元組成的序列(\ + \n)。
當 Bash 看到這個序列時,會直接把它從程式碼中移除,藉此用下一行來_接續_當前行。
小心不要在反斜線和換行之間留下任何空白。
再看一個例子
printf "%s\n" "${numbers[@]}" | bc --mathlib | sort --general-numeric-sort
或是
printf "%s\n" "${numbers[@]}" \
| bc --mathlib \
| sort --general-numeric-sort