軌道
/
Bash
Bash
/
課程大綱
/
管線與指令清單
管線

管線與指令清單 在 Bash

{other: "%{count} 個練習"}

關於 管線與指令清單

我們已經看過如何撰寫簡單的指令,也就是指令後面接著引數。 現在我們要來看如何組合簡單的指令,做出更複雜的指令。

輸入與輸出

在開始之前,先快速介紹一下輸入與輸出。

行程有「標準 I/O 通道」。

  • 行程可以在「stdin」上讀取_輸入_。
  • 行程可以在「stdout」上產生_輸出_。
  • 行程可以在「stderr」上產生_錯誤輸出_。

tr指令就是一個很純粹的例子。 它所做的只是從輸入讀取文字、進行字元轉換,然後把轉換後的文字印到輸出。

我們之後會在課程大綱裡看到更多關於操作「stdio」通道的內容。

管線

這是 shell 程式設計的「殺手級功能」之一。 管線讓你能對文字串流做出精巧的轉換。

要產生一份排序後的使用者清單:

cat /etc/passwd | cut -d : -f 1 | sort

管線符號(|)會把一個指令的輸出連接到另一個指令的輸入。 cut會讀取cat的輸出,而sort會讀取cut的輸出。

Advanced
  • 預設情況下,管線中的每個指令都會在各自獨立的子 shell 中執行。 (子 shell 是一個子行程,也就是目前執行中 shell 的複本。)

  • 執行管線是有效能成本的。 如果你發現自己寫出由相似指令組成的長管線,不妨考慮把它們合併成單一指令。 舉例來說,用到多個grep、cut、sed、awk和tr的管線,通常可以合併成單一個awk指令來提升效率。

  • 管線的結束狀態,就是管線中最後一個指令的結束狀態。 這表示即使前面有指令失敗了,只要最後一個指令成功,整條管線的結束狀態就是 0。

    有一個 shell 設定可以改變這點; 如果啟用了「pipefail」設定(使用set -o pipefail),管線的結束狀態就會是:

    • 所有指令都成功時為 0,否則
    • 管線中各指令裡_最後一個不為 0 的結束狀態_。

指令清單

指令清單是一連串的管線,以;(或換行)、&&或||分隔。

  • A; B是一種指令清單,其中B會在A完成之後執行。
  • A && B,其中B只在A成功(以狀態 0 結束)時執行。
  • A || B,其中B只在A失敗(以不為 0 的狀態結束)時執行。

指令清單的結束狀態,就是最後被執行的那個指令的結束狀態。

&&和||運算子可以串接起來,讓下一個指令根據前面指令的狀態有條件地執行。 例如

A && B && C && D || E
  • 如果 A 成功,B 就會執行,
  • 如果 A 和 B 都成功,C 就會執行,
  • 如果 A、B、C 都成功,D 就會執行,
  • 如果 A、B、C、D 中任何一個失敗,E 就會執行。
Caution

請謹慎使用這些邏輯運算子。 它們很快就會導致程式碼難以閱讀,或是邏輯難以理解。

舉例來說,你可能會以為下面這兩個是一樣的:

if A; then B; else C; fi
A && B || C

差別在於:C 什麼時候會執行?

  • 在第一段程式碼(if敘述)中,唯有 A 失敗時,C 才會執行。
  • 在第二段程式碼中,C 會在 A 失敗_或 A 成功但 B 失敗_時執行!

指令清單的用途

這裡有幾個例子,說明指令清單如何簡化程式碼。

從檔案讀取成塊的多行資料

假設你有一個包含三角形資料的資料檔, 而每個三角形用三行分別存放它的三個邊。

$ 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")
Note

_續行_就是「反斜線」和「換行」這兩個字元組成的序列(\ + \n)。 當 Bash 看到這個序列時,會直接把它從程式碼中移除,藉此用下一行來_接續_當前行。 小心不要在反斜線和換行之間留下任何空白。

再看一個例子

printf "%s\n" "${numbers[@]}" | bc --mathlib | sort --general-numeric-sort

或是

printf "%s\n" "${numbers[@]}" \
| bc --mathlib \
| sort --general-numeric-sort
透過 GitHub 編輯 連結會在新視窗或分頁中開啟