|
Daniel Robbins (drobbins@gentoo.org), 總裁兼 CEO, Gentoo Technologies, Inc. 2000 年 3 月 01 日 通 過(guò)學(xué)習如何使用 bash 腳本語(yǔ)言編程,將使 Linux 的日常交互更有趣和有生產(chǎn)力,同時(shí)還可以利用那些已熟悉和喜愛(ài)的標準 UNIX 概念(如管道和重定向)。在此三部分系列中,Daniel Robbins 將以示例指導您如何用 bash 編程。他將講述非?;镜闹R(這使此系列十分適合初學(xué)者),并在后續系列中逐步引入更高級特性。 您可能要問(wèn):為什么要學(xué)習 Bash 編程?好,以下是幾條令人信服的理由: 已經(jīng)在運行它 如果查看一下,可能會(huì )發(fā)現:您現在正在運行 bash。因為 bash 是標準 Linux shell,并用于各種目的,所以,即使更改了缺省 shell,bash 可能 仍 在系統中某處運行。因為 bash 已在運行,以后運行的任何 bash 腳本都天生是有效利用內存的,因為它們與任何已運行的 bash 進(jìn)程共享內存。如果正在運行的工具可以勝任工作,并且做得很好,為什么還要裝入一個(gè) 500K 的解釋器?
已經(jīng)在使用它 不僅在運行 bash,實(shí)際上,您每天還在與 bash 打交道。它總在那里,因此學(xué)習如何最大限度使用它是有意義的。這樣做將使您的 bash 經(jīng)驗更有趣和有生產(chǎn)力。但是為什么要學(xué)習 bash 編程 ?很簡(jiǎn)單,因為您已在考慮如何運行命令、CPing 文件以及管道化和重定向輸出。為什么不學(xué)習一種語(yǔ)言,以便使用和利用那些已熟悉和喜愛(ài)的強大省時(shí)的概念?命令 shell 開(kāi)啟了 UNIX 系統的潛能,而 bash 正是 這個(gè) Linux shell。它是您和機器之間的高級紐帶。增長(cháng) bash 知識吧,這將自動(dòng)提高您在 Linux 和 UNIX 中的生產(chǎn)力 -- 就那么簡(jiǎn)單。
Bash 困惑 以錯誤方式學(xué)習 bash 令人十分困惑。許多新手輸入 "man bash" 來(lái)查看 bash 幫助頁(yè),但只得到非常簡(jiǎn)單和技術(shù)方面的 shell 功能性描述。還有人輸入 "info bash"(來(lái)查看 GNU 信息文檔),只能得到重新顯示的幫助頁(yè),或者(如果幸運)略為友好的信息文檔。 盡管這可能使初學(xué)者有些失望,但標準 bash 文檔無(wú)法滿(mǎn)足所有人的要求,它只適合那些已大體熟悉 shell 編程的人。幫助頁(yè)中確實(shí)有很多極好的技術(shù)信息,但對初學(xué)者的幫助卻有限。 這就是本系列的目的所在。在本系列中,我將講述如何實(shí)際使用 bash 編程概念,以便編寫(xiě)自己的腳本。與技術(shù)描述不同,我將以簡(jiǎn)單的語(yǔ)言為您解釋?zhuān)鼓粌H知道事情做什么,還知道應在何時(shí)使用。在此三部分系列末尾,您將可以自己編寫(xiě)復雜的 bash 腳本,并可以自如地使用 bash 以及通過(guò)閱讀(和理解)標準 bash 文檔來(lái)補充知識。讓我們開(kāi)始吧。
環(huán)境變量 在 bash 和幾乎所有其它 shell 中,用戶(hù)可以定義環(huán)境變量,這些環(huán)境變量在以 ASCII 字符串存儲。環(huán)境變量的最便利之處在于:它們是 UNIX 進(jìn)程模型的標準部分。這意味著(zhù):環(huán)境變量不僅由 shell 腳本獨用,而且還可以由編譯過(guò)的標準程序使用。當在 bash 中“導出”環(huán)境變量時(shí),以后運行的任何程序,不管是不是 shell 腳本,都可以讀取設置。一個(gè)很好的例子是 vipw 命令,它通常允許 root 用戶(hù)編輯系統口令文件。通過(guò)將 EDITOR 環(huán)境變量設置成喜愛(ài)的文本編輯器名稱(chēng),可以配置 vipw,使其使用該編輯器,而不使用 vi,如果習慣于 xemacs 而確實(shí)不喜歡 vi,那么這是很便利的。 在 bash 中定義環(huán)境變量的標準方法是:
以上命令定義了一個(gè)名為 "myvar" 的環(huán)境變量,并包含字符串 "This is my environment variable!"。以上有幾點(diǎn)注意事項:第一,在等號 "=" 的兩邊沒(méi)有空格,任何空格將導致錯誤(試一下看看)。第二個(gè)件要注意的事是:雖然在定義一個(gè)字時(shí)可以省略引號,但是當定義的環(huán)境變量值多于一個(gè)字時(shí)(包含空格或制表鍵),引號是必須的。
第三,雖然通??梢杂秒p引號來(lái)替代單引號,但在上例中,這樣做會(huì )導致錯誤。為什么呢?因為使用單引號禁用了稱(chēng)為擴展的 bash 特性,其中,特殊字符和字符系列由值替換。例如,"!" 字符是歷史擴展字符,bash 通常將其替換為前面輸入的命令。(本系列文章中將不講述歷史擴展,因為它在 bash 編程中不常用。有關(guān)歷史擴展的詳細信息,請參閱 bash 幫助頁(yè)中的“歷史擴展”一節。)盡管這個(gè)類(lèi)似于宏的功能很便利,但我們現在只想在環(huán)境變量后面加上一個(gè)簡(jiǎn)單的感嘆號,而不是宏。 現在,讓我們看一下如何實(shí)際使用環(huán)境變量。這有一個(gè)例子:
通過(guò)在環(huán)境變量的前面加上一個(gè) $,可以使 bash 用 myvar 的值替換它。這在 bash 術(shù)語(yǔ)中叫做“變量擴展”。但是,這樣做將怎樣:
我們希望回顯 "fooThis is my environment variable!bar",但卻不是這樣。錯在哪里?簡(jiǎn)單地說(shuō),bash 變量擴展設施陷入了困惑。它無(wú)法識別要擴展哪一個(gè)變量:$m、$my、$myvar 、$myvarbar 等等。如何更明確清楚地告述 bash 引用哪一個(gè)變量?試一下這個(gè):
如您所見(jiàn),當環(huán)境變量沒(méi)有與周?chē)谋久黠@分開(kāi)時(shí),可以用花括號將它括起。雖然 $myvar 可以更快輸入,并且在大多數情況下正確工作,但 ${myvar} 卻能在幾乎所有情況下正確通過(guò)語(yǔ)法分析。除此之外,二者相同,將在本系列的余下部分看到變量擴展的兩種形式。請記?。寒敪h(huán)境變量沒(méi)有用空白(空格或制表鍵)與周?chē)谋痉珠_(kāi)時(shí),請使用更明確的花括號形式。 回想一下,我們還提到過(guò)可以“導出”變量。當導出環(huán)境變量時(shí),它可以自動(dòng)地由以后運行的任何腳本或可執行程序環(huán)境使用。shell 腳本可以使用 shell 的內置環(huán)境變量支持“到達”環(huán)境變量,而 C 程序可以使用 getenv() 函數調用。這里有一些 C 代碼示例,輸入并編譯它們 -- 它將幫助我們從 C 的角度理解環(huán)境變量: myvar.c -- 樣本環(huán)境變量 C 程序
將上面的代碼保存到文件 myenv.c 中,然后發(fā)出以下命令進(jìn)行編譯:
現在,目錄中將有一個(gè)可執行程序,它在運行時(shí)將打印 EDITOR 環(huán)境變量的值(如果有值的話(huà))。這是在我機器上運行時(shí)的情況:
啊... 因為沒(méi)有將 EDITOR 環(huán)境變量設置成任何值,所以 C 程序得到一個(gè)空字符串。讓我們試著(zhù)將它設置成特定值:
雖然希望 myenv 打印值 "xemacs",但是因為還沒(méi)有導出環(huán)境變量,所以它卻沒(méi)有很好地工作。這次讓它正確工作:
現在,如您親眼所見(jiàn):不導出環(huán)境變量,另一個(gè)進(jìn)程(在本例中是示例 C 程序)就看不到環(huán)境變量。順便提一句,如果愿意,可以在一行定義并導出環(huán)境變量,如下所示:
這與兩行版本的效果相同?,F在該演示如何使用 unset 來(lái)除去環(huán)境變量:
截斷字符串概述 截斷字符串是將初始字符串截斷成較小的獨立塊,它是一般 shell 腳本每天執行的任務(wù)之一。很多時(shí)候,shell 腳本需要采用全限定路徑,并找到結束的文件或目錄。雖然可以用 bash 編碼實(shí)現(而且有趣),但標準 basename UNIX 可執行程序可以極好地完成此工作:
Basename 是一個(gè)截斷字符串的極簡(jiǎn)便工具。它的相關(guān)命令 dirname 返回 basename 丟棄的“另”一部分路徑。
命令替換 需要知道一個(gè)簡(jiǎn)便操作:如何創(chuàng )建一個(gè)包含可執行命令結果的環(huán)境變量。這很容易:
上面所做的稱(chēng)為“命令替換”。此例中有幾點(diǎn)需要指出。在第一行,簡(jiǎn)單地將要執行的命令以 反引號 括起。那不是標準的單引號,而是鍵盤(pán)中通常位于 Tab 鍵之上的單引號??梢杂?bash 備用命令替換語(yǔ)法來(lái)做同樣的事:
如您所見(jiàn),bash 提供多種方法來(lái)執行完全一樣的操作。使用命令替換可以將任何命令或命令管道放在 ` ` 或 $( ) 之間,并將其分配給環(huán)境變量。真方便!下面是一個(gè)例子,演示如何在命令替換中使用管道:
象專(zhuān)業(yè)人員那樣截斷字符串 盡管 basename 和 dirname 是很好的工具,但有時(shí)可能需要執行更高級的字符串“截斷”,而不只是標準的路徑名操作。當需要更強的說(shuō)服力時(shí),可以利用 bash 內置的變量擴展功能。已經(jīng)使用了類(lèi)似于 ${MYVAR} 的標準類(lèi)型的變量擴展。但是 bash 自身也可以執行一些便利的字符串截斷??匆幌逻@些例子:
在第一個(gè)例子中,輸入了 ${MYVAR##*fo}。它的確切含義是什么?基本上,在 ${ } 中輸入環(huán)境變量名稱(chēng),兩個(gè) ##,然后是通配符 ("*fo")。然后,bash 取得 MYVAR,找到從字符串 "foodforthought.jpg" 開(kāi)始處開(kāi)始、且匹配通配符 "*fo" 的 最長(cháng) 子字符串,然后將其從字符串的開(kāi)始處截去。剛開(kāi)始理解時(shí)會(huì )有些困難,為了感受一下這個(gè)特殊的 "##" 選項如何工作,讓我們一步步地看看 bash 如何完成這個(gè)擴展。首先,它從 "foodforthought.jpg" 的開(kāi)始處搜索與 "*fo" 通配符匹配的子字符串。以下是檢查到的子字符串:
在搜索了匹配的字符串之后,可以看到 bash 找到兩個(gè)匹配。它選擇最長(cháng)的匹配,從初始字符串的開(kāi)始處除去,然后返回結果。 上面所示的第二個(gè)變量擴展形式看起來(lái)與第一個(gè)相同,但是它只使用一個(gè) "#" -- 并且 bash 執行 幾乎 同樣的過(guò)程。它查看與第一個(gè)例子相同的子字符串系列,但是 bash 從初始字符串除去 最短 的匹配,然后返回結果。所以,一查到 "fo" 子字符串,它就從字符串中除去 "fo",然后返回 "odforthought.jpg"。 這樣說(shuō)可能會(huì )令人十分困惑,下面以一簡(jiǎn)單方式記住這個(gè)功能。當搜索最長(cháng)匹配時(shí),使用 ##(因為 ## 比 # 長(cháng))。當搜索最短匹配時(shí),使用 #???,不難記吧!等一下,怎樣記住應該使用 ‘#‘ 字符來(lái)從字符串開(kāi)始部分除去?很簡(jiǎn)單!注意到了嗎:在美國鍵盤(pán)上,shift-4 是 "$",它是 bash 變量擴展字符。在鍵盤(pán)上,緊靠 "$" 左邊的是 "#"。這樣,可以看到:"#" 位于 "$" 的“開(kāi)始處”,因此(根據我們的記憶法),"#" 從字符串的開(kāi)始處除去字符。您可能要問(wèn):如何從字符串末尾除去字符。如果猜到我們使用美國鍵盤(pán)上緊靠 "$" 右邊 的字符 ("%),那就猜對了。這里有一些簡(jiǎn)單的例子,解釋如何截去字符串的末尾部分:
正如您所見(jiàn),除了將匹配通配符從字符串末尾除去之外,% 和 %% 變量擴展選項與 # 和 ## 的工作方式相同。請注意:如果要從末尾除去特定子字符串,不必使用 "*" 字符:
在此例中,使用 "%%" 或 "%" 并不重要,因為只能有一個(gè)匹配。還要記?。喝绻浟藨撌褂?"#" 還是 "%",則看一下鍵盤(pán)上的 3、4 和 5 鍵,然后猜出來(lái)。 可以根據特定字符偏移和長(cháng)度,使用另一種形式的變量擴展,來(lái)選擇特定子字符串。試著(zhù)在 bash 中輸入以下行:
這種形式的字符串截斷非常簡(jiǎn)便,只需用冒號分開(kāi)來(lái)指定起始字符和子字符串長(cháng)度。
應用字符串截斷 現在我們已經(jīng)學(xué)習了所有截斷字符串的知識,下面寫(xiě)一個(gè)簡(jiǎn)單短小的 shell 腳本。我們的腳本將接受一個(gè)文件作為自變量,然后打?。涸撐募欠袷且粋€(gè) tar 文件。要確定它是否是 tar 文件,將在文件末尾查找模式 ".tar"。如下所示: mytar.sh -- 一個(gè)簡(jiǎn)單的腳本
要運行此腳本,將它輸入到文件 mytar.sh 中,然后輸入 "chmod 755 mytar.sh",生成可執行文件。然后,如下做一下 tar 文件試驗:
好,成功運行,但是不太實(shí)用。在使它更實(shí)用之前,先看一下上面使用的 "if" 語(yǔ)句。語(yǔ)句中使用了一個(gè)布爾表達式。在 bash 中,"=" 比較運算符檢查字符串是否相等。在 bash 中,所有布爾表達式都用方括號括起。但是布爾表達式實(shí)際上測試什么?讓我們看一下左邊。根據前面所學(xué)的字符串截斷知識,"${1##*.}" 將從環(huán)境變量 "1" 包含的字符串開(kāi)始部分除去最長(cháng)的 "*." 匹配,并返回結果。這將返回文件中最后一個(gè) "." 之后的所有部分。顯然,如果文件以 ".tar" 結束,結果將是 "tar",條件也為真。 您可能會(huì )想:開(kāi)始處的 "1" 環(huán)境變量是什么。很簡(jiǎn)單 -- $1 是傳給腳本的第一個(gè)命令行自變量,$2 是第二個(gè),以此類(lèi)推。好,已經(jīng)回顧了功能,下面來(lái)初探 "if" 語(yǔ)句。
If 語(yǔ)句 與大多數語(yǔ)言一樣,bash 有自己的條件形式。在使用時(shí),要遵循以上格式;即,將 "if" 和 "then" 放在不同行,并使 "else" 和結束處必需的 "fi" 與它們水平對齊。這將使代碼易于閱讀和調試。除了 "if,else" 形式之外,還有其它形式的 "if" 語(yǔ)句:
只有當
以上 "elif" 形式將連續測試每個(gè)條件,并執行符合第一個(gè) 真 條件的操作。如果沒(méi)有條件為真,則將執行 "else" 操作,如果有一個(gè)條件為真,則繼續執行整個(gè) "if,elif,else" 語(yǔ)句之后的行。
下一次 我們已經(jīng)學(xué)習了最基本的 bash 功能,現在要加快腳步,準備編寫(xiě)一些實(shí)際腳本。在下一篇中,將講述循環(huán)概念、函數、名稱(chēng)空間和其它重要主題。然后,將準備好編寫(xiě)一些更復雜的腳本。在第三篇中,將重點(diǎn)講述一些非常復雜的腳本和功能,以及幾個(gè) bash 腳本設計選項。再見(jiàn)! 參考資料
關(guān)于作者
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
聯(lián)系客服