快速學會 shell 編程

在過去幾十年中所出現的UNIX和類UNIX操做系統家族已經成爲現在最爲流行、使用最普遍的操做系統之一,這都算不上什麼祕密了。對於使用了多年UNIX的程序員而言,一切都瓜熟蒂落:UNIX系統爲程序開發提供了既優雅又高效的環境。這正是Dennis Ritchie和Ken Thompson在20世紀60年代晚期在貝爾實驗室開發UNIX時的初衷。程序員

在本書中,咱們使用的術語UNIX泛指基於UNIX的操做系統你們族,其中包括像Solaris這樣真正的UNIX操做系統以及像Linux和Mac OS X這樣的類UNIX操做系統。shell

UNIX系統最重要的特性之一就是各式各樣的程序。超過200個基本命令會隨着標準操做系統發行,Linux還對標準命令數量作了擴充,一般能達到700~1000個!這些命令(也稱爲工具)從統計文件行數、發送電子郵件到顯示特定年份的日曆,可謂無所不能。編程

不過UNIX真正的威力並不是來自數量龐大的命令,而在於你能夠很是輕鬆、優雅地將這些命令組合在一塊兒完成很是複雜的任務。數組

UNIX的標準用戶界面是命令行,其實就是Shell,它的角色是做爲用戶和系統最底層之間(內核)的緩衝帶。Shell就是一個程序,讀入用戶輸入的命令,將其轉換成系統更易於理解的形式。它還包括了一些核心編程構件,能夠作出判斷、執行循環以及爲變量儲值。安全

從AT&T發行版(源自Stephen Bourne在貝爾實驗室編寫的第一版)開始,標準Shell就是同UNIX系統捆綁在一塊兒的。自那時起,IEEE根據Bourne Shell以及後續的一些其餘Shell制訂了標準。該標準目前的(本書寫做之時)版本是Shell and Utilities volume of IEEE Std 1003.1-2001,也稱爲POSIX標準。本書餘下的內容都離不開Shell。
在本章中,你將學習到什麼是UNIX的Shell,Shell可以作什麼,以及爲何說它是每一個高級用戶工具箱中不可或缺的一部分。20分鐘輕鬆學會shell很容易,不過若是想要全面掌握還須要專業的書籍來深度學習。
本文摘自《UNIX/Linux/OS X中的Shell編程(第4版)》bash


試讀地址:
www.epubit.com.cn/book/online…

2.1 內核和實用工具

UNIX系統在邏輯上被劃分爲兩個不一樣的部分:內核和實用工具(Utility),如圖2.1所示。或者你也能夠認爲是內核和其餘部分,一般來講,全部的訪問都要經由Shell。服務器

圖2.1 UNIX系統網絡

內核是UNIX系統的核心所在,當打開計算機並啓動(booted)以後,內核就位於計算機的內存中,直到關機爲止。ssh

組成完整的UNIX系統的各類實用工具位於計算機磁盤中,在須要的時候會被加載到內存中並執行。實際上你所知道的全部UNIX命令都是實用工具,所以這些命令所對應的程序也都在磁盤上,僅在須要時纔會被載入內存。舉例來講,當你執行date命令時,UNIX系統會將名爲date的程序從磁盤上載入到內存中,讀取其代碼來執行特定的操做。編程語言

Shell也是一個實用工具程序,它做爲登陸過程的一部分被載入到內存中執行。實際上,有必要了解當終端或終端窗口中的第一個Shell啓動時所發生的一系列事件。

2.2 登陸Shell

在早期,終端是一個物理設備,經過線纜鏈接到安裝了UNIX系統的硬件上。而現在,終端程序可以讓你停留在Linux、Mac或Windows環境內部,在受控窗口(managed window)中同網絡上的設備交互。一般來講,你會啓動如Terminal或xterm這類程序,而後在須要的時候利用sshtelnetrlogin鏈接到遠程系統。

對於系統上的每一個物理終端,都會激活一個叫做getty的程序,如圖2.2所示。

圖2.2 getty進程

只要系統容許用戶登陸,UNIX系統(更準確地說,應該是叫做init的程序)就會在每一個終端端口自動啓動一個getty程序。getty是一個設備驅動程序,可以讓login程序在其所分配的終端上顯示login:,等待用戶輸入內容。

若是你是經過ssh這類程序來鏈接的,會分配到一個僞終端或僞tty。這就是爲何在輸入who命令時會看到有相似於ptty3pty1這樣的條目。

在這兩種狀況下,會有程序讀取帳戶和密碼信息,對這些信息進行驗證,若是沒有問題的話,就調用登陸所需的登陸程序。

只要輸入相應字符並敲下Enter鍵,login程序就完成了登陸過程(見圖2.3)。

login開始執行時,它會在終端上顯示字符串Password:,而後等待用戶輸入密碼。完成輸入並按下Enter鍵後(出於安全性的考慮,你在屏幕上看不到輸入的內容),login會比對文件/etc/passwd中相應的條目來驗證登陸名和密碼。每一個用戶在該文件中都有對應的條目,其中包括了登陸名、主目錄以及用戶登陸後要啓動的程序。最後一部分信息(登陸Shell)存儲在每行最後一個冒號以後。若是這個冒號後面沒有內容,則默認使用標準Shell,即/bin/sh

圖2.3 用戶sue終端上啓動的login

若是是經過終端程序登陸,數據交換也許會涉及系統上的程序(如ssh)和服務器上的程序(如sshd),要是你在本身的UNIX計算機上打開了窗口,可能不須要再次輸入密碼就可以馬上登入。很是方便!

把話題轉回密碼文件。下面3行展現了/etc/passwd文件內容的典型形式,對應着系統用戶:suepatbob

sue:*:15:47::/users/sue:
pat:*:99:7::/users/pat:/bin/ksh
bob:*:13:100::/users/data:/users/data/bin/data_entry複製代碼

login將所輸入密碼的加密形式與特定帳戶保存在/etc/shadow中的加密形式進行比對以後,若是沒有問題,它會檢查要執行的登陸程序的名稱。在絕大多數狀況下,這個登陸程序會是/bin/sh/bin/ksh/bin/bash。在少數狀況下,可能會是一個特殊的定製程序或者/bin/nologin,後者用於不能進行交互式訪問的帳戶(經常使用於文件全部權管理)。其背後的理念就是你能夠爲登陸帳戶進行設置,使其登陸到系統以後可以自動運行指定的程序。大多數時候指定的程序都是Shell,畢竟它是一種通用的實用工具,不過這並不是是惟一的選擇。

來看用戶sue。一旦該用戶經過驗證,login會結束掉自身,將控制權轉交給sue的終端鏈接,該鏈接與標準Shell相連,而後login就從內存中消失了(見圖2.4)。

按照以前/etc/passwd文件中顯示的其餘條目,pat獲得的是存儲在/bin下的ksh(這是Korn Shell),bob獲得的是一個名爲data_entry的指定程序(見圖2.5)。

圖2.4 login執行/usr/bin/sh

圖2.5 3個登陸的用戶

以前提到過,init程序會針對網絡鏈接運行相似於getty的程序。例如,sshdtelnetdrlogind會響應來自sshtelnetrlogin的鏈接請求。這些程序並無直接和特定的物理終端或調制解調器線路聯繫在一塊兒,而是將用戶的Shell鏈接到僞終端上。你能夠在X Window系統的窗口中或使用who命令查看是否已經經過網絡或聯網的終端鏈接登陸到了系統中:

$ who
phw      pts/0    Jul 20 17:37          使用rlogin登陸
$複製代碼

2.3 在Shell中輸入命令

當Shell啓動時,它會在終端中顯示出一個命令行提示符,一般是美圓符$,而後等待用戶輸入命令(圖2.6中的第1步和第2步)。每次輸入命令並按Enter鍵(第3步),Shell就會分析輸入的內容,而後執行所請求的操做(第4步)。

若是你要求Shell調用某個程序,Shell會搜索磁盤,查找環境變量PATH中指定的全部目錄,直到找到指定的程序。找到了該程序後,Shell會將本身複製一份(稱爲子Shell),讓內核使用指定的程序替換這個子Shell,接着登陸Shell就會「休眠」,等待被調用的程序執行完畢(第5步)。內核將指定程序複製到內存中並開始執行。這個複製過來的程序稱爲進程。程序和進程之間是有區別的,前者是保存在磁盤上的文件,然後者位於內存中並被逐行執行。

若是程序將輸出寫入到標準輸出中,那麼輸出內容會出如今終端裏,除非你將其重定向或經過管道導向其餘命令。與此相似,若是程序從標準輸入中讀取輸入,那麼它會等着你輸入內容,除非輸入被重定向到了另外一個文件或經過管道從其餘命令導入(第6步)。

當命令執行完畢後,就會從內存中消失,控制權再次交給登陸Shell,它會提示你輸入下一條命令(第7步和第8步)。

圖2.6 命令執行週期

注意,只要你沒有登出系統,這個週期就會周而復始下去。若是登出系統,Shell就會終止執行,系統將會啓動一個新的getty(或者rlogind等)並等待其餘用戶登入,如圖2.7所示。

重要的是要認識到Shell就是一個程序而已。它在系統中沒有什麼特權,也就是說,只要有足夠的專業技術和熱情,任何人均可以建立本身的Shell。這就是爲何現在會有這麼多不一樣風格的Shell,其中包括由Stephen Bourne開發的古老的Bourne Shell、由David Korn開發的KornShell、主要用於Linux系統的Bourne again Shell以及由Bill Joy開發的C Shell。這些Shell都旨在應對特定的需求,各自都有本身獨特的功能和特點。

圖2.7 登陸週期

2.4 Shell的職責

如今你知道了Shell會分析(用計算機行話來講,就是解析)輸入的每一行命令,而後執行指定的程序。在解析期間,文件名中的特殊字符(如*)會被擴展,就像第一章講到的那樣。

Shell還有其餘的職責,如圖2.8所示。

圖2.8 Shell的職責

2.4.1 程序執行

Shell負責執行你在終端中指定的全部程序。

每次輸入一行內容,Shell就會分析該行,而後決定執行什麼操做。就Shell而言,每一行都遵循如下基本格式:

program-name arguments複製代碼

說得更正式些,輸入的這一行叫作命令行。Shell會掃描該命令行,肯定要執行的程序名稱及所傳入的程序參數。

Shell使用一些特殊字符來肯定程序名稱及每一個參數的起止。這些字符統稱爲空白字符(whitespace characters),它們包括空格符、水平製表符和行尾符(更正式的叫法是換行符)。連續的多個空白字符會被Shell忽略。若是你輸入命令

mv    tmp/mazewars games複製代碼

Shell會掃描該命令行,提取行首到第一個空白字符之間的全部內容做爲待執行的程序名稱:mv。隨後的空白字符(多餘的空格)會被忽略,直到下一個空白字符之間的字符做爲mv的第一個參數:tmp/mazewars。再到下一個空白字符(在本例中是換行符)之間的字符做爲mv的第二個參數:games。解析完命令行以後,Shell就開始執行mv命令,其中包括兩個指定的參數:tmp/mazewarsgames(見圖2.9)。

圖2.9 執行帶有兩個參數的mv命令

剛纔提到過,多個空白字符會被Shell忽略。這意味着當Shell處理下面的命令行時:

echo            when   do        we      eat? 複製代碼

會向echo程序傳遞4個參數:whendoweeat?(見圖2.10)。

圖2.10 執行帶有4個參數的echo命令

echo會提取命令參數並將其顯示在終端中,所以在輸出的參數之間加上一個空格會使得命令輸出變得更易讀:

$ echo when do we eat? 
when do we eat? 
$複製代碼

結果證實echo命令徹底看不到這些空白字符,它們都被Shell給「沒收」了。等到第5章講引用的時候,你就知道該如何把空白字符包含到程序參數中了,不過,一般來講,去掉這些多餘的空白字符正是咱們想要的作法。

咱們以前講到過,Shell會搜索磁盤,直到找到須要執行的程序爲止,而後由UNIX內核負責程序的執行。在大多數時候,的確如此。但有些命令其實是內建於Shell自身中的。這些內建命令包括cdpwdecho。Shell在磁盤中搜索命令以前,它首先會判斷該命令是否爲內建命令,若是是的話,就直接執行。

不過在調用命令以前,Shell還有點事須要處理,所以,讓咱們先來討論一下這方面的內容。

2.4.2 變量及文件名替換

和比較正式的編程語言同樣,Shell容許將值賦給變量。只要你在命令行中將某個變量放在美圓符號$以後,Shell就會將該變量替換成對應的變量值。咱們會在第4章中詳細討論這個話題。

除此以外,Shell還會在命令行中執行文件名替換。實際上Shell,在肯定要執行的程序及其參數以前,會掃描命令行,從中查找文件名替換字符*?[...]

假設當前目錄下包含這些文件:

$ ls
mrs.todd
prog1
shortcut
sweeney
$複製代碼

如今讓咱們在echo命令中使用文件名替換(*):

$ echo `*```               列出全部文件
mrs.todd prog1 shortcut Sweeney
$複製代碼

咱們給echo程序傳入了幾個參數?1個仍是4個?由於Shell會執行文件名替換,因此答案是4個。當Shell分析下列命令行時

echo *複製代碼

它識別出了特殊字符*,將其替換成當前目錄下的全部文件名(甚至還會將這些文件名依字母順序排列):

echo mrs.todd prog1 shortcut sweeney複製代碼

而後Shell決定將哪些參數傳給實際的命令。所以,echo根本不知道星號*的存在,它只知道命令行上有4個參數(見圖2.11)。

圖2.11 執行echo

2.4.3 I/O重定向

Shell還要負責處理輸入/輸出重定向。它會掃描每個命令行,從中查找特殊的重定向字符<>>>(若是你以爲好奇的話,還有一個重定向序列<<,你會在第12章中學到相關的內容)。

若是你輸入命令

echo Remember to record The Walking Dead > reminder複製代碼

Shell會識別出特殊的輸出重定向字符>,而後將命令行中的下一個單詞做爲輸出重定向所指向的文件名。在本例中,這個文件名爲reminder。若是reminder已經存在且用戶具備寫權限,那麼文件中已有的內容會被覆蓋掉。若是沒有該文件或其所在目錄的寫權限,Shell會產生錯誤信息。

在Shell執行程序以前,它會將程序的標準輸出重定向到指定的文件。在大多數狀況下,程序根本不知道本身的輸出被重定向了。它仍照舊向標準輸出中寫入(這一般是終端),意識不到Shell已經將信息重定向到了文件中。

讓咱們來看兩個幾乎同樣的命令:

$ wc -l users
      5 users
$ wc -l < users
      5
$複製代碼

在第一個例子中,Shell解析命令行,肯定要執行的程序名稱是wc併爲其傳入兩個參數:-lusers(見圖2.12)。

圖2.12 執行wc -l users

wc執行時,會看到傳入的兩個參數。第一個參數是-l,告訴它須要統計行數。第二個參數指定了待統計行數的文件。所以wc會打開文件users,統計行數,而後打印出結果及對應的文件名。

第二個例子中的wc操做略有不一樣。Shell在掃描命令行時發現了輸入重定向字符<,其後的單詞就被解釋成從中重定向輸入的文件名。從命令行中提取出了「< users」以後,Shell就開始執行wc程序,將其標準輸入重定向爲文件users並傳入單個參數-l(見圖2.13)。

圖2.13 執行wc -l < users

此次當wc執行時,它會看到傳入的單個參數-l。由於沒有指定文件名,wc會轉而去統計標準輸入中內容的行數。所以wc -l在統計行數時,並不知道它其實是在對文件users進行統計。最後的顯示結果和平時同樣,可是缺乏了文件名,由於咱們並無爲wc指定。

要理解兩條命令在執行上的不一樣,這一點很是重要。若是還不太清楚,那麼在繼續閱讀以前複習一下上面的內容。

2.4.4 管道

Shell在掃描命令行時,除了重定向符號以外還會查找管道字符|。每找到一個,就會將以前命令的標準輸出鏈接到以後命令的標準輸入,而後執行這兩個命令。

若是你輸入

who | wc -l複製代碼

Shell會查找分隔了命令whowc的管道符號。它將上一個命令的標準輸出鏈接到下一個命令的標準輸入,而後執行二者。who命令執行時會生成已登陸用戶列表並將結果寫入標準輸出,它並不知道輸出內容並無出如今終端而是進入了另外一個命令。

wc命令執行時,它發現並無指定文件名,所以就對標準輸入內容進行統計,並無意識到標準輸入並不是來自終端,而是來自於who命令的輸出。

隨着本書內容的深刻,你會看到管道中並不只限於有兩條命令,你能夠在複雜的管道中將3條、4條、5條甚至更多的命令串聯在一塊兒。這多少有點很差理解,但倒是UNIX系統強大威力的所在。

2.4.5 環境控制

Shell提供了一些可以定製我的環境的命令。我的環境包括主目錄、命令行提示符以及用於搜索待執行程序的目錄列表。咱們會在第10章中對此展開詳述。

2.4.6 解釋型編程語言

Shell有本身內建的編程語言。這種語言是解釋型的,也就是說,Shell會分析所遇到的每一條語句,而後執行所發現的有效的命令。這與C++及Swift這類編程語言不一樣,在這些語言中,程序語句在執行以前一般會被編譯成可由機器執行的形式。

相較於編譯型語言,由解釋型語言所編寫的程序通常要更易於調試和修改。然而,所花費的時間要比實現相同功能的編譯型語言程序更長。

Shell編程語言提供了可在大多數其餘編程語言中找到的其餘特性。它有循環結構、決策語句、變量、函數,並且是面向過程的。基於IEEE POSIX標準的現代Shell還有許多其餘特性,包括數組、

相關文章
相關標籤/搜索