1977:Bourne Shell(sh)問世#
事件#
1977 年,貝爾實驗室的 Stephen Bourne 發表 Bourne Shell(程式名仍為 sh),隨 UNIX 第七版(Version 7 Unix)問世。這是 shell script 語言史上最重要的一年:現代 shell 腳本語法至此奠定,五十年來幾乎所有 shell 都以它為祖先。
新語法的年份、理論與實用原因#
1. 變數與參數展開(name=value、$name)#
- 理論原因:shell 要成為「語言」而非「命令列表」,變數是狀態的最低要求。
- 實用原因:路徑、檔名、設定值可以集中管理(如
$PATH),腳本可移植。 - 彌補缺陷:Thompson Shell 完全沒有變數,同樣的值必須重複寫死。
2. 控制流程:if/then/elif/else/fi#
- 理論原因:條件判斷是圖靈完備性的最低要求。
- 實用原因:腳本可以「檔案存在才執行」「參數正確才繼續」。
- 彌補缺陷:Mashey Shell(1975)的
if是粗糙的巨集展開;Thompson Shell 完全無條件判斷。 - 語法特色:Bourne 故意用
fi、esac、done收尾——他刻意不採用 C 語法(當時 shell 用 Algol 風格的前置處理器寫成),讓語法在視覺上與 C 程式區隔,腳本讀起來像英文敘述。
3. 迴圈:for ... in ... do ... done、while ... do ... done#
- 理論原因:重複執行是腳本自動化的本質。
- 實用原因:批次處理檔案(
for f in *.txt; do ...)是系統管理最常見的需求。 - 彌補缺陷:以前只能用外部工具(如
find的-exec)或重複呼叫腳本。
4. 多重分支:case ... esac#
- 理論原因:多重分支是結構化程式設計(Dijkstra 學派)的標準配備。
- 實用原因:處理選項(
-a、-b、-c)或系統類型分支時比一串if清晰。 - 彌補缺陷:巢狀
if的可讀性災難。
5. 函數(1978/1980 加入)#
- 理論原因:結構化程式設計的函數抽象,避免重複程式碼。
- 實用原因:大型系統啟動腳本(如
/etc/rc)需要模組化。 - 彌補缺陷:只能靠「呼叫另一支腳本」模擬函數,開銷大且無回傳值。
6. Here-document(<<)——1977 年發明#
- 發明年份:1977 年由 Stephen Bourne 在 Bourne Shell 中發明(
<<語法),shell 於 1979 年隨 UNIX 第 7 版正式對外發佈。這是 shell 語言史上「多行文字內嵌」語法的誕生點,之後 Perl(1987)、Ruby(1995)、PHP(1995)等語言都直接繼承了<<EOF的語法形式。 - 理論原因:把「多行文字」內嵌在腳本中作為某程式的標準輸入——文字流(1973)與重導向(1971)的組合必然產物。
- 實用原因:產生設定檔、送郵件、內嵌文件、批次建立測試輸入。
- 彌補缺陷:多行輸入必須另建暫存檔,既慢(磁碟 I/O)又要在腳本中管理暫存檔的生命週期。
- 後續演化:bash 3.0(2004)加入 here-string
<<<(單行文字內嵌,不需 EOF 結尾);zsh 支援未閉合引號的 EOF 防展開變體。
7. 環境變數(export)#
- 理論原因:行程的環境(environment)概念——子行程繼承父行程的狀態。
- 實用原因:
PATH、TERM等設定可傳遞給所有子程式,成為 UNIX 設定的通用機制。 - 彌補缺陷:一般變數無法跨行程傳遞。
8. 標準輸入/輸出/錯誤與 2> 重導向#
- 實用原因:錯誤訊息與正常輸出分流,管線才不會被錯誤訊息污染。
- 彌補缺陷:Thompson/早期 shell 只有一個輸出流。
語法設計的爭議#
Bourne 拒絕 C 語法(if (x) {})換來了腳本可讀性,但代價是後來的 csh 使用者抱怨 Bourne 語法「不像 C」,這直接催生了 1978 年的 C Shell,也埋下了「csh 好交互、sh 好腳本」的長期分裂。
程式範例:Bourne Shell 的語法全貌#
#!/bin/sh
# 1. 變數與參數展開:狀態集中管理
BACKUP_DIR=/backup
SRC=$1 # 腳本參數
echo "備份 $SRC 到 $BACKUP_DIR"
# 2. 條件判斷:檔案存在才執行
if [ -d "$SRC" ]; then
echo "目錄存在"
elif [ -f "$SRC" ]; then
echo "是檔案"
else
echo "不存在"; exit 1
fi
# 3. 迴圈:批次處理檔案
for f in *.txt; do
cp "$f" "$BACKUP_DIR/$f" # 引號防止空格檔名出錯
done
# while 迴圈:逐行讀檔
while read line; do
echo "處理: $line"
done < list.txt
# 4. 多重分支:處理選項
case "$1" in
-a) echo "全部" ;;
-b) echo "備份" ;;
*) echo "用法: $0 [-a|-b]"; exit 1 ;;
esac
# 5. 函數:模組化
usage() {
echo "用法: $0 <來源> <目的>"
}
# 6. Here-document(1977 年發明):多行文字內嵌
mail -s "備份完成" admin <<EOF
備份作業已完成
時間: $(date)
EOF
# here-doc 中「quoted EOF」防止變數展開
cat <<'EOF' > config.txt
路徑是 $PATH(原文輸出,不展開)
EOF
# 2004 年 bash 3.0 的 here-string `<<<`:單行內嵌,不需 EOF 結尾
grep "$USER" <<< "$(who)"
tr a-z A-Z <<< "hello"
# 7. 環境變數:跨行程傳遞設定
export PATH="$PATH:/usr/local/bin" # 子行程繼承
# 8. 錯誤分流:stdout 與 stderr 分開
prog > out.txt 2> err.txt
prog > all.txt 2>&1 # 2>&1 表示「stderr 併入 stdout」
彌補了什麼缺陷#
彌補了 Thompson/Mashey Shell「無變數、無控制流程、無函數」的缺陷,讓 shell 從命令列表升級為圖靈完備的腳本語言,並成為 1992 年 POSIX shell 標準的直接藍本。