Linux 外殼的演變之旅
對于大多數的日常計算任務來說,鼠標的點擊操作就可以滿足要求了,但要真正利用到Linux相比于其他環境的 優勢的話,則最終還是需要弄懂系統的外殼程序來輸入命令行才行。可用的命令外殼程序有很多,從Bash和Korn到C shell外殼,以及各種各樣有著異域風情的和奇怪的外殼程序等不一而足。我們來了解一下哪一種外殼程序是適用于你的。
外殼或外殼程序(shell)就像是編輯器:每個人都有自己的喜好,并且會為自己的選擇進行強烈的辯護(并會告訴你為什么你應該換用)。誠然,一些外殼程序可以提供不同的功能,但它們都實現了幾十年前就已經形成的核心理念。
關 于現代的外殼程序,我的***使用體驗發生在1980年代,當時我正在SunOS上開發軟件。一旦了解了把一個程序的輸出用作另一個程序的輸入(甚至可在命 令鏈中多次這樣做)這種能力之后,我就擁有了一種簡單有效的創建過濾器和轉換的方式。這一核心思想提供了一種構建簡單工具的方式,這些工具足夠靈活,能夠 以一種有益的組合來和其他工具一起使用。通過這種方式,外殼程序不僅提供了一種與內核和設備交換的方式,而且整合了多種服務(比如說管道和過濾器),這類 服務現在在軟件開發中已是常見的設計模式了。
我們先從現代外殼程序的簡單歷史開始,然后探討Linux目前提供的一些有用的以及一些奇異的外殼程序。
外殼程序的歷史
外殼程序——或稱作命令行解釋器——有著一個很長的歷史,但這里的討論從***個 UNIX®外殼程序開始。(貝爾實驗室的)Ken Thompson在1971年開發了名為V6 shell的 ***UNIX外殼程序。與其在Multics上的前身相類似,這個外殼程序(/bin/sh)是一個獨立的用戶程序,在內核的外部執行。諸如通配符(參數 擴展的模式匹配,比如說*.txt)一類的概念被放在一個名為glob的單獨的實用程序中實現,就像是if命令計算條件表達式一樣。這種分割維持了外殼程 序的短小精悍,只有不到900行的C源代碼(參閱參考資料獲得到初始源代碼的鏈接)。
外殼程序為重定向(<>和>>)和管道(|或^)引入了一種緊湊的語法,這些語法仍然在現代的外殼程序中使用。你也依然能夠找到對調用順序命令(使用;分隔)和異步命令(使用&分隔)的支持。
Thompson的外殼程序所缺少的是編寫腳本的功能,它的唯一目就是作為一種交換式外殼(命令解釋器)來調用命令然后查看結果。
自1977年以來的UNIX外殼程序
在Thompson外殼之后,我們從1977年的現代外殼程序來開始這一了解過程,Bourne外殼在這一年被引入。Bourne 外殼是AT&T貝爾實驗室的Stephen Bourne為V7 UNIX創建的,至今還保留了一個可用的外殼程序(在某些情況下,作為默認的根用戶執行外殼(root shell))。該作者是在進行了ALGOL68編譯器方面的工作之后才開發Bourne外殼的,所以你會發現其語法比其他外殼程序更類似于算法語言 (Algorithmic Language ,ALGOL),而源代碼本身,盡管是用C來開發的,甚至使用了宏來賦予它一種ALGOL68的味道。
Bourne外殼有兩個主要的目的:作為一個命令解釋器,以交互方式執行操作 系統的命令;以及用來編寫腳本(編寫可通過外殼調用的可重用腳本)。除了取代Thompson外殼的功能之外,Bourne外殼還提供了一些超越其前任的 優勢。Bourne引入了控制流、循環和變量,提供了一種更函數化的語言來與操作系統交互(對 話式的或是非對話式的都可以)。該外殼程序還允許你把外殼腳本當成過濾器使用,為處理信號提供集成的支持,不過其缺乏定義函數的功能。***一點是,該外殼 程序納入了一些我們今天還在使用的功能,其中包括了命令替換(使用反引號),以及在腳本內部嵌入保留的串字面量的HERE文檔。
Bourne外殼不僅是前進道路上的很重要的一步,而且是多種派生出來的外殼 程序的基石,這些派生外殼中的許多今天仍然用在一些典型的Linux系統上。圖1說明了一些重要的外殼程序的傳承關系,Bourne外殼帶來了Korn外 殼(ksh)、Almquist外殼(ash)流行的Bourne Again Shell(或稱Bash)的發展;而當Bourne外殼發布時,C shell外殼程序(csh)已在開發之中。圖1說明了主要的傳承關系,但并未包含了所有的影響,一些跨多個外殼的顯著貢獻在這里并未標注出來。
圖1. 自1977年以來的Linux外殼
我們稍后會探討其中的一些外殼程序,并例舉出一些對它們的發展有貢獻作用的語言和功能。
基本的外殼程序架構
設想中的外殼程序的基礎架構很簡單(已由Bourne外殼證明),正如你在圖2中見到的那樣,基本的架構看起來類似一個管道,其中的輸入是分析和 解析,接著是符號的擴充(使用各種各樣的方法,比如說括號、波浪線、變量和參數的擴展和替換,以及文件名生成等),以及***的命令執行(使用外殼內置的命 令或是外部命令)。
圖2. 假想外殼程序的簡單架構
你可在參考資料一節找到找到有關鏈接,了解開源的Bash外殼的架構。
探討Linux的外殼程序
現在我們來探討一下幾個這樣的外殼程序,回顧它們所做出的貢獻,并在每個外殼程序中檢驗一個腳本例子。要查看的外殼程序包括了C shell、Korn 外殼和Bash。
Tenex C shell外殼
1978年,當Bill Joy還是加州大學伯克利分校的在校學生時,他為Berkeley Software Distribution (BSD) UNIX系統開發了C shell。五年之后,該外殼引入了Tenex系統(在DEC PDP系統上很流行)上的功能。除了命令行編輯功能之外,Tenex還引入了文件名稱和命令的補全功能。Tenex C shell(tcsh)保持了對csh的向后兼容,但提升了其整體的交互功能。tcsh是Ken Greer在卡內基 - 梅隆大學開發出來的。
C shell的一個主要設計目標是創建一種看上去類似于C語言的腳本語言,鑒于C當時是在用的主要語言(加之操作系統絕大部分都是使用C來開發的),所以這是一個很實用的目標。
Bill Joy帶到C shell中的一個實用功能是命令的歷史記錄,這一功能維持之前執行過的命令的一個歷史,并允許用戶查看并輕松地選擇前面的命令來執行。例如,輸入命令 history就會顯示出之前執行過的命令,使用上下箭頭按鍵來選擇命令,或是使用!!來執行前面的一個命令。引用前一個命令的所有參數也是可以的,比如 說,!*引用前一個命令的所有參數,而!$則是引用前一個命令的***一個參數。
看一下一個簡短的tcsh腳本例子(清單1),該腳本用到了一個參數(目錄名稱),給出該目錄下的所有可執行文件和找到的文件的數目。我在每個例子中都重用了這一腳本,以此來說明一些不同之處。
該tcsh腳本被分成了三個基本的部分,首先,需要注意的是,我是使用了shebang或稱作hashbang的符號(#!)來聲明這一文件是可 被外殼執行程序(在本例中是tcsh二進制執行文件)解釋的,這就可以讓我把該文件當成一個普通的可執行文件來執行,而不需要在它之前加上解釋器的二進制 文件名。腳本維持了一個找到的可執行文件的計數,所以我把這一計數初始化為零。
清單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來檢查文件是否是可執行的,如果是的話,輸出該文件名稱并且計數加一。在腳本的末尾,我 輸出可執行文件的數目。
Korn外殼
Korn外殼(Korn shell,ksh)由David Korn設計,其差不多是和Tenex C shell同一時期引入的。Korn外殼最吸引人的功能之一是被當成腳本語言使用,與此同時還向后兼容最初的Bourne外殼。
Korn外殼原來是專有軟件,直到2000年的時候,它才(遵照通用公共許可協議)作為開源軟件發布。除了提供很強的向后兼容Bourne外殼的 功能之外,Korn外殼還包含了一些來自其他外殼的功能(比如說csh的歷史記錄功能)。該外殼還提供了一些更先進的功能,這些功能可以在諸如Ruby和 Python一類的現代腳本語言中找到——比如說,關聯數組和浮點運算。Korn外殼在許多操作系統上都是可用的,這些系統中就包括了IBM® AIX® and HP-UX;并且盡力去支持 Portable Operating System Interface for UNIX(POSIX)外殼語言的標準。
Korn外殼是從Bourne外殼派生而來的,因此其看上去更類似于Bourne外殼和Bash而不是C shell。我們來看一個Korn外殼的查找可執行文件的例子(清單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相類似。就結構上來說,腳本幾乎就是相同的,主要的不同體現在條件語句、表達式和遍歷的執行方式上。ksh并未采用類C的測試運算符,其采用了典型的Bourne式的運算符(-eq、-ne、-lt等)。
Korn外殼在遍歷方面也有些不同,在korn外殼中,所用的是for in結構,其使用了命令替換來表示文件列表,該文件列表通過命令ls '$1/*的標準輸出來創建,而該命令則代表了指定名字的子目錄中的內容。
除了前面明確了的其他功能之外,Korn還支持別名功能(使用用戶定義的串來替代一個詞)。Korn有許多其他功能在默認情況下是禁用的(比如說文件名稱的補全),不過這些功能可由用戶來啟用。
Bourne-Again Shell外殼
Bourne-Again Shell,或稱作Bash,是一個開源的GNU項目,其目標是取代Bourne外殼,Bash是由Brian Fox開發出來的,其已成為最常提供的外殼之一(在Linux、Darwin、Windows®、Cygwin、Novell、Haiku等等之上都有它 的身影)。顧名思義,Bash是Bourne外殼的一個超集,大多數的Bourne腳本都可不做修改就能執行。
【編輯推薦】