從 Bash 和 Korn 到 C shell:評估 Linux 中的 shell

原諒地址:作者:M. Tim Jones,來源:IBMDW


目錄

Shell 就像編輯器一樣:每個人都有自己喜歡的選擇並極力爲該選擇辯護(還告訴您爲什麼應該使用該選擇)。確實如此,shell 可提供不同的功能,但它們都實現了數十年前開發的核心理念。

  我第一次使用現代 shell 是在二十世紀 80 年代,當時我正在 SunOS 上開發軟件。當我瞭解了將一個程序的輸出用作另一個程序的輸入(甚至多次連環地使用)的能力後,我就有了一種簡單且高效的方式來創建過濾器和轉換。該核心理念提供了一種方式來構建一些簡單工具,這些工具足夠靈活,可與其他工具組合使用。通過這種方式,shell 不僅提供了一種與內核和設備交互的方式,還提供了現在作爲軟件開發中的常見設計模式的集成服務(比如管道和過濾器)。

  讓我們首先簡單介紹一下現代 shell 的發展歷史,然後探討如今一些可用於 Linux 的外來的有用 shell。

shell 的發展歷史

  Shell(或命令行解釋器)具有很長的歷史了,但我們的討論從第一個 UNIX® shell 開始。(貝爾實驗室的)Ken Thompson 於 1971 年開發了第一個用於 UNIX 的 shell,名爲 V6 shell。類似於它在 Multics 中的前身,這個 shell (/bin/sh) 是一個在內核外部執行的獨立用戶程序。globbing(參數擴展的模式匹配,比如 *.txt)等概念是在一個名爲 glob的獨立實用程序中實現的,就像用於評估條件表達式的 if 命令一樣。這種獨立性可保持 shell 很小,只需不到 900 行 C 源代碼(請參見 參考資料 獲取原始源代碼的鏈接)。

  該 shell 爲重定向(< > 和 >>)和管道(| 或 ^)引入了一種緊湊的語法,這種語法已延續到現代 shell 中。您也可以找到對調用順序命令(使用 ;)和異步命令(使用 &)的支持。

  Thompson shell 缺少的是編寫腳本的能力。它的唯一用途就是用作一個交互式 shell(命令解釋器)來調用命令和查看結果。

1977 年以來的 UNIX shell

  撇開 Thompson shell,我們開始將目光轉移到 1977 年引入 Bourne shell 時的現代 shell。Bourne shell 由 Stephen Bourne 在 AT&T Bell Labs 爲 V7 UNIX 創建,它在如今仍然是一個有用的 shell(在一些情況下還被用作默認的根 shell)。作者在研究 ALGOL68 編譯器之後開發了 Bourne shell,所以您會發現它的語法比其他 shell 更加類似於 Algorithmic Language (ALGOL)。儘管使用 C 開發,源代碼本身甚至使用了宏賦予它一種 ALGOL68 特色。

  Bourne shell 有兩個主要目標:用作一個命令解釋器來交互式執行操作系統命令,以及用於編寫腳本(編寫可通過 shell 調用的可重用腳本)。 除了取代 Thompson shell,Bourne shell 還提供了相對於其前身的多項優勢。Bourne 向腳本中引入了控制流、循環和變量,提供了一種更加強大的語言來與操作系統交互(包括交互式和非交互式)。該 shell 還允許您使用 shell 腳本作爲過濾器,提供對處理信號的集成支持,但缺乏定義函數的能力。 最後,它整合了我們如今使用的許多功能,包括命令替換(使用反引號)和用於將保留的字符串文字嵌入到腳本中的 HERE 文檔。

  Bourne shell 不僅是向前發展的重要一步,也是衆多衍生的 shell 的基礎,其中許多 shell 如今應用在典型的 Linux 系統中。圖 1 演示了重要 shell 的系列。Bourne shell 導致了 Korn shell (ksh)、Almquist shell (ash) 和流行的 Bourne Again Shell(或 Bash)的開發。在 Bourne shell 發佈時,C shell (csh) 正在開發。圖 1 顯示了主要系列,但沒有展示所有影響,也沒有展示一些具有重要貢獻的 shell。

  圖 1. 1977 年以來的 Linux shell

  shell 的 “家族樹”

  我們稍後將分析其中一些 shell,查看爲它們的進步做出貢獻的語言和功能示例。

基本 shell 架構

  一種假想的 shell 的基本架構很簡單(Bourne 的 shell 就是一個證據)。在圖 2 中可以看到,基本架構看起來類似一個管道,其中會分析和解析輸入,展開符號(使用各種方法,比如括號、波浪號、變量、參數擴展和替換,以及文件名生成),最終執行命令(使用 shell 內置的命令或外部命令)。

  圖 2. 假想 shell 的簡單架構

  假想 shell 的簡單架構圖,包括內核

  在 參考資料 部分中,您可以找到一些鏈接來了解開源 Bash shell 的架構。

探索 Linux shell

  現在讓我們看看其中一些 shell,回顧它們所做的貢獻並在每個 shell 中查看示例腳本。查看的內容包括 C shell、Korn shell 和 Bash。

1. Tenex C shell

  C shell 是 Bill Joy 1978 年在加州大學伯克利分校攻讀研究生期間爲 Berkeley SoftwareDistribution (BSD) UNIX 系統開發的。5 年後,該 shell 引入了來自 Tenex 系統(在 DEC PDP 系統上很流行)的功能。Tenex 引入了文件名和命令完成功能,以及命令行編輯功能。Tenex C shell (tcsh) 仍然向後兼容 csh,但改進了它的整體交互式功能。tcsh 是 Ken Greer 在 Carnegie Mellon University 開發的。

  該 C shell 的一個重要的設計目標是創建一種類似 C 語言的腳本語言。這是一個有用的目標,因爲 C 語言是所使用的主要語言(此外,該操作系統也是主要使用 C 語言開發的)。

  Bill Joy 在 C shell 中引用的一項有用功能是命令歷史。此功能維護以前執行的命令的歷史,允許用戶檢查並輕鬆選擇之前的命令來執行。例如,鍵入命令 history 將顯示以前執行的命令。可使用向上和向下箭頭來選擇命令,或者可以使用 !! 執行前一個命令。也可以引用以前的命令的參數,例如 !* 引用前一個命令的所有參數,其中 !$ 引用前一個命令的最後一個參數。

  看一下 tcsh 腳本的一個簡單示例(清單 1)。這段腳本獲取一個參數(一個目錄名稱),輸出該目錄中的所有可執行文件以及找到的文件數量。我將在每個示例中重用此腳本設計來演示區別。

  tcsh 腳本可分解爲 3 個基本部分。首先,請注意,我使用了 shebang 或 hashbang 符號來將此文件聲明爲可由定義的 shell 可執行文件(在本例中爲 tcsh 二進制文件)解釋。這允許我以常規可執行文件的形式執行該文件,而不在它之前添加解釋器二進制文件。它維護找到的可執行文件數量,所以我將此數量初始化爲 0。

  清單 1. 用 tcsh 編寫的查找所有可執行文件的腳本

  #!/bin/tcsh

  # find all executables

  set count=0

  # Test arguments

  if ($#argv != 1) then

  echo "Usage is $0

"

  exit 1

  endif

  # Ensure argument is a directory

  if (! -d $1) then

  echo "$1 is not a directory."

  exit 1

  endif

  # Iterate the directory, emit executable files

  foreach filename ($1/*)

  if (-x $filename) then

  echo $filename

  @ count = $count + 1

  endif

  end

  echo

  echo "$count executable files found."

  exit 0

  第一部分測試用戶傳遞的參數。#argv 變量表示傳入的參數數量(不包括命令名稱本身)。您可指定這些參數的索引來訪問它們:例如,#1 表示第一個參數(它是 argv[1] 的簡寫)。該腳本需要一個參數;如果它未找到該參數,則輸出一條錯誤消息,使用 $0 表示在控制檯輸入的命令名稱(argv[0])。

  第二部分確保傳入的參數是一個目錄。如果該參數是一個目錄,-d 操作符返回 True。但請注意,我首先指定了一個 ! 符號,這表示無效。這樣,表達式可表明,如果參數不是一個目錄,則輸出一條錯誤消息。

  最後一部分迭代目錄中的文件,以測試它們是否可執行文件。我使用方便的 foreach 迭代器,它循環括號(在本例中爲該目錄)中的每一項,然後在循環中測試每一項。這一步使用 -x 操作符測試文件是否爲可執行文件,如果是,則輸出該文件並將數量加一。在腳本的末尾,我輸出可執行文件的數量。

2. Korn shell

  Korn shell (ksh) 由 David Korn 設計,是在與 Tenex C shell 相同的時期引入的。Korn shell 的一項最有趣的功能是,它除了向後兼容原始的 Bourne shell,還可用作腳本語言。

  Korn shell 在 2000 年以開源形式發佈(依據 Common Public License)以前一直是專用的軟件。除了提供與 Bourne shell 強大的向後兼容性,Korn shell 還包含其他 shell 的功能(比如 csh 的歷史功能)。該 shell 還提供了可在現代腳本語言(比如 Ruby 和 Python)中找到的一些更加高級的功能 — 例如,關聯數組和浮點算法。Korn shell 可用於多種操作系統,包括 IBM® AIX® 和 HP-UX,致力於支持 Portable Operating System Interface for UNIX (POSIX) shell 語言標準。

  Korn shell 是 Bourne shell 的衍生物,因此看上去更像 Bourne shell 和 Bash 而不是 C shell。讓我們看一個查找可執行文件的 Korn shell 示例(清單 2)。

  清單 2. 用 ksh 編寫的查找所有可執行文件的腳本

  #!/usr/bin/ksh

  # find all executables

  count=0

  # Test arguments

  if [ $# -ne 1 ] ; then

  echo "Usage is $0

"


  exit 1

  fi

  # Ensure argument is a directory

  if [ ! -d "$1" ] ; then

  echo "$1 is not a directory."

  exit 1

  fi

  # Iterate the directory, emit executable files

  for filename in "$1"/*

  do

  if [ -x "$filename" ] ; then

  echo $filename

  count=$((count+1))

  fi

  done

  echo

  echo "$count executable files found."

  exit 0

  在清單 2 中,您將注意到的第一點是它與 清單 1 的相似性。在結構上,該腳本基本上是相同的,但在執行條件、表達式和迭代的方式上存在明顯的區別。沒有采用類似 C 的測試操作符,ksh 採用了典型的 Bourne 風格操作符(-eq、-ne 和 -lt 等)。

  Korn shell 也有用一些與迭代相關的區別。在 Korn shell 中,使用了 for in 結構,使用命令替換來表示從命令 ls ‘$1/*(表示指定子目錄的內容)的標準輸出創建的文件列表。

  除了上面定義的其他功能,Korn 還支持別名功能(用於將一個詞替換爲用戶定義的字符串)。Korn 還有其他許多功能默認已禁用(比如文件名稱完成),但可由用戶啓用。

3. Bourne-Again Shell

  Bourne-Again Shell(或 Bash)是一個開源 GNU 項目,旨在取代 Bourne shell。Bash 由 Brian Fox 開發,已成爲世上最流行的 shell 之一(出現在 Linux、Darwin、Windows®、Cygwin、Novell、Haiku 等系統中)。顧名思義,Bash 是 Bourne shell 的一個超集,大部分 Bourne 腳本都可原封不動地執行。

  除了支持腳本的向後兼容性,Bash 還整合了來自 Korn 和 C shell 的功能。您將找到命令歷史、命令行編輯、一個目錄棧(pushd 和popd)、許多有用的環境變量和命令完成等。

  Bash 繼續在發展,擁有許多新功能,支持正則表達式(類似於 Perl)和關聯數組。儘管其中一些功能可能在其他腳本語言中不存在,但可以編寫兼容其他語言的腳本。對於這一點,清單 3 中所示的示例腳本等同於 Korn shell 腳本(來自 清單 2),除了 shebang 區別 (/bin/bash)。

  清單 3. 用 Bash 編寫的查找所有可執行文件的腳本

  #!/bin/bash

  # find all executables

  count=0

  # Test arguments

  if [ $# -ne 1 ] ; then

  echo "Usage is $0

"


  exit 1

  fi

  # Ensure argument is a directory

  if [ ! -d "$1" ] ; then

  echo "$1 is not a directory."

  exit 1

  fi

  # Iterate the directory, emit executable files

  for filename in "$1"/*

  do

  if [ -x "$filename" ] ; then

  echo $filename

  count=$((count+1))

  fi

  done

  echo

  echo "$count executable files found."

  exit 0

  這些 shell 之間的一個關鍵區別是它們的發佈所依據的許可。您可能已猜到,Bash 是在 GNU 項目中開發的,是依據 GPL 發佈的,而 csh、tcsh、zsh、ash 和 scsh 都是依據 BSD 或一種類似 BSD 的許可來發布的。Korn shell 可依據 Common Public License 使用。

4. 外來的 shell:Scheme shell (scsh)

  對於大膽的開發人員,可基於您的需要或愛好使用替代的 shell。Scheme shell (scsh) 提供了一種使用 Scheme(Lisp 語言的一種衍生物)的腳本環境。Pyshell 是對創建使用 Python 語言的類似腳本的一次嘗試。最後,對於嵌入式系統,可以使用 BusyBox,它將一個 shell 和所有命令合併到一個二進制文件中,以簡化其分發和管理。

  清單 4 給出了以 Scheme shell (scsh) 編寫的查找所有可執行文件的腳本。這段腳本可能看起來很奇怪,但它實現了與我們目前所提供的腳本類似的功能。這段腳本包含 3 個函數,直接使用可執行代碼(在末尾)來測試參數數量。這段腳本真正強大之處在showfiles 函數內,它迭代一個列表(在 with-cwd 後構造),在列表的每個元素後調用 write-ln。這個列表通過迭代指定的目錄並在其中過濾是可執行文件的文件來生成。

  清單 4. 用 scsh 編寫的查找所有可執行文件的腳本

  #!/usr/bin/scsh -s

  !#

  (define argc

  (length command-line-arguments))

  (define (write-ln x)

  (display x) (newline))

  (define (showfiles dir)

  (for-each write-ln

  (with-cwd dir

  (filter file-executable? (directory-files "." #t)))))

  (if (not (= argc 1))

  (write-ln "Usage is fae.scsh dir")

  (showfiles (argv 1)))

結束語

  早期 shell 的許多理念和大量接口在之後的 35 年幾乎未變 — 這是對早期 shell 的原始作者的貢獻的有力證明。在一個不斷自我改造的行業中,shell 已大大改進,但沒有發生重大變化。儘管存在過創建特殊 shell 的嘗試,但 Bourne shell 的衍生物仍然是所使用的主要 shell。

發佈了39 篇原創文章 · 獲贊 7 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章