shell初步(轉)

zhouwf0726發表於2019-05-19

Bash 例項,第一部分
Bourne again shell (bash) 基本程式設計


文件選項
將此頁作為電子郵件傳送

最新推薦
Java 應用開發源動力 - 下載免費軟體,快速啟動開發


級別: 初級

Daniel Robbins, 總裁兼 CEO, Gentoo Technologies, Inc.


2000 年 3 月 01 日

通過學習如何使用 bash 指令碼語言程式設計,將使 Linux 的日常互動更有趣和有生產力,同時還可以利用那些已熟悉和喜愛的標準 UNIX 概念(如管道和重定向)。在此三部分系列中,Daniel Robbins 將以示例指導您如何用 bash 程式設計。他將講述非常基本的知識(這使此係列十分適合初學者),並在後續系列中逐步引入更高階特性。
您可能要問:為什麼要學習 Bash 程式設計?好,以下是幾條令人信服的理由:

已經在執行它

如果檢視一下,可能會發現:您現在正在執行 bash。因為 bash 是標準 Linux shell,並用於各種目的,所以,即使更改了預設 shell,bash 可能 仍 在系統中某處執行。因為 bash 已在執行,以後執行的任何 bash 指令碼都天生是有效利用記憶體的,因為它們與任何已執行的 bash 程式共享記憶體。如果正在執行的工具可以勝任工作,並且做得很好,為什麼還要裝入一個 500K 的直譯器?


回頁首

已經在使用它

不僅在執行 bash,實際上,您每天還在與 bash 打交道。它總在那裡,因此學習如何最大限度使用它是有意義的。這樣做將使您的 bash 經驗更有趣和有生產力。但是為什麼要學習 bash 程式設計 ?很簡單,因為您已在考慮如何執行命令、CPing 檔案以及管道化和重定向輸出。為什麼不學習一種語言,以便使用和利用那些已熟悉和喜愛的強大省時的概念?命令 shell 開啟了 UNIX 系統的潛能,而 bash 正是 這個 Linux shell。它是您和機器之間的高階紐帶。增長 bash 知識吧,這將自動提高您在 Linux 和 UNIX 中的生產力 -- 就那麼簡單。


回頁首

Bash 困惑

以錯誤方式學習 bash 令人十分困惑。許多新手輸入 "man bash" 來檢視 bash 幫助頁,但只得到非常簡單和技術方面的 shell 功能性描述。還有人輸入 "info bash"(來檢視 GNU 資訊文件),只能得到重新顯示的幫助頁,或者(如果幸運)略為友好的資訊文件。

儘管這可能使初學者有些失望,但標準 bash 文件無法滿足所有人的要求,它只適合那些已大體熟悉 shell 程式設計的人。幫助頁中確實有很多極好的技術資訊,但對初學者的幫助卻有限。

這就是本系列的目的所在。在本系列中,我將講述如何實際使用 bash 程式設計概念,以便編寫自己的指令碼。與技術描述不同,我將以簡單的語言為您解釋,使您不僅知道事情做什麼,還知道應在何時使用。在此三部分系列末尾,您將可以自己編寫複雜的 bash 指令碼,並可以自如地使用 bash 以及通過閱讀(和理解)標準 bash 文件來補充知識。讓我們開始吧。


回頁首

環境變數

在 bash 和幾乎所有其它 shell 中,使用者可以定義環境變數,這些環境變數在以 ASCII 字串儲存。環境變數的最便利之處在於:它們是 UNIX 程式模型的標準部分。這意味著:環境變數不僅由 shell 指令碼獨用,而且還可以由編譯過的標準程式使用。當在 bash 中“匯出”環境變數時,以後執行的任何程式,不管是不是 shell 指令碼,都可以讀取設定。一個很好的例子是 vipw 命令,它通常允許 root 使用者編輯系統口令檔案。通過將 EDITOR 環境變數設定成喜愛的文字編輯器名稱,可以配置 vipw,使其使用該編輯器,而不使用 vi,如果習慣於 xemacs 而確實不喜歡 vi,那麼這是很便利的。

在 bash 中定義環境變數的標準方法是:


$ myvar='This is my environment variable!'


以上命令定義了一個名為 "myvar" 的環境變數,幷包含字串 "This is my environment variable!"。以上有幾點注意事項:第一,在等號 "=" 的兩邊沒有空格,任何空格將導致錯誤(試一下看看)。第二個件要注意的事是:雖然在定義一個字時可以省略引號,但是當定義的環境變數值多於一個字時(包含空格或製表鍵),引號是必須的。

引用細節

有關如何在 bash 中使用引號的非常詳盡的資訊,請參閱 bash 幫助頁面中的“引用”一節。特殊字元序列由其它值“擴充套件”(替換)確實使 bash 中字串的處理變得複雜。本系列將只講述最常用的引用功能。


第三,雖然通常可以用雙引號來替代單引號,但在上例中,這樣做會導致錯誤。為什麼呢?因為使用單引號禁用了稱為擴充套件的 bash 特性,其中,特殊字元和字元系列由值替換。例如,"!" 字元是歷史擴充套件字元,bash 通常將其替換為前面輸入的命令。(本系列文章中將不講述歷史擴充套件,因為它在 bash 程式設計中不常用。有關歷史擴充套件的詳細資訊,請參閱 bash 幫助頁中的“歷史擴充套件”一節。)儘管這個類似於巨集的功能很便利,但我們現在只想在環境變數後面加上一個簡單的感嘆號,而不是巨集。

現在,讓我們看一下如何實際使用環境變數。這有一個例子:


$ echo $myvar
This is my environment variable!


通過在環境變數的前面加上一個 $,可以使 bash 用 myvar 的值替換它。這在 bash 術語中叫做“變數擴充套件”。但是,這樣做將怎樣:


$ echo foo$myvarbar
foo


我們希望回顯 "fooThis is my environment variable!bar",但卻不是這樣。錯在哪裡?簡單地說,bash 變數擴充套件設施陷入了困惑。它無法識別要擴充套件哪一個變數:$m、$my、$myvar 、$myvarbar 等等。如何更明確清楚地告述 bash 引用哪一個變數?試一下這個:


$ echo foo${myvar}bar
fooThis is my environment variable!bar


如您所見,當環境變數沒有與周圍文字明顯分開時,可以用花括號將它括起。雖然 $myvar 可以更快輸入,並且在大多數情況下正確工作,但 ${myvar} 卻能在幾乎所有情況下正確通過語法分析。除此之外,二者相同,將在本系列的餘下部分看到變數擴充套件的兩種形式。請記住:當環境變數沒有用空白(空格或製表鍵)與周圍文字分開時,請使用更明確的花括號形式。

回想一下,我們還提到過可以“匯出”變數。當匯出環境變數時,它可以自動地由以後執行的任何指令碼或可執行程式環境使用。shell 指令碼可以使用 shell 的內建環境變數支援“到達”環境變數,而 C 程式可以使用 getenv() 函式呼叫。這裡有一些 C 程式碼示例,輸入並編譯它們 -- 它將幫助我們從 C 的角度理解環境變數:


myvar.c -- 樣本環境變數 C 程式

#include
#include
int main(void) {
char *myenvvar=getenv("EDITOR");
printf("The editor environment variable is set to %sn",myenvvar);
}


將上面的程式碼儲存到檔案 myenv.c 中,然後發出以下命令進行編譯:


$ gcc myenv.c -o myenv


現在,目錄中將有一個可執行程式,它在執行時將列印 EDITOR 環境變數的值(如果有值的話)。這是在我機器上執行時的情況:


$ ./myenv
The editor environment variable is set to (null)


啊... 因為沒有將 EDITOR 環境變數設定成任何值,所以 C 程式得到一個空字串。讓我們試著將它設定成特定值:


$ EDITOR=xemacs
$ ./myenv
The editor environment variable is set to (null)


雖然希望 myenv 列印值 "xemacs",但是因為還沒有匯出環境變數,所以它卻沒有很好地工作。這次讓它正確工作:


$ export EDITOR
$ ./myenv
The editor environment variable is set to xemacs


現在,如您親眼所見:不匯出環境變數,另一個程式(在本例中是示例 C 程式)就看不到環境變數。順便提一句,如果願意,可以在一行定義並匯出環境變數,如下所示:


$ export EDITOR=xemacs


這與兩行版本的效果相同。現在該演示如何使用 unset 來除去環境變數:


$ unset EDITOR
$ ./myenv
The editor environment variable is set to (null)

dirname 和 basename

請注意:dirname 和 basename 不是磁碟上的檔案或目錄,它們只是字串操作命令。



回頁首

截斷字串概述

截斷字串是將初始字串截斷成較小的獨立塊,它是一般 shell 指令碼每天執行的任務之一。很多時候,shell 指令碼需要採用全限定路徑,並找到結束的檔案或目錄。雖然可以用 bash 編碼實現(而且有趣),但標準 basename UNIX 可執行程式可以極好地完成此工作:


$ basename /usr/local/share/doc/foo/foo.txt
foo.txt
$ basename /usr/home/drobbins
drobbins


Basename 是一個截斷字串的極簡便工具。它的相關命令 dirname 返回 basename 丟棄的“另”一部分路徑。


$ dirname /usr/local/share/doc/foo/foo.txt
/usr/local/share/doc/foo
$ dirname /usr/home/drobbins/
/usr/home


回頁首

命令替換

需要知道一個簡便操作:如何建立一個包含可執行命令結果的環境變數。這很容易:


$ MYDIR=`dirname /usr/local/share/doc/foo/foo.txt`
$ echo $MYDIR
/usr/local/share/doc/foo


上面所做的稱為“命令替換”。此例中有幾點需要指出。在第一行,簡單地將要執行的命令以 反引號 括起。那不是標準的單引號,而是鍵盤中通常位於 Tab 鍵之上的單引號。可以用 bash 備用命令替換語法來做同樣的事:


$ MYDIR=$(dirname /usr/local/share/doc/foo/foo.txt)
$ echo $MYDIR
/usr/local/share/doc/foo


如您所見,bash 提供多種方法來執行完全一樣的操作。使用命令替換可以將任何命令或命令管道放在 ` ` 或 $( ) 之間,並將其分配給環境變數。真方便!下面是一個例子,演示如何在命令替換中使用管道:


MYFILES=$(ls /etc | grep pa)
bash-2.03$ echo $MYFILES
pam.d passwd


回頁首

象專業人員那樣截斷字串

儘管 basename 和 dirname 是很好的工具,但有時可能需要執行更高階的字串“截斷”,而不只是標準的路徑名操作。當需要更強的說服力時,可以利用 bash 內建的變數擴充套件功能。已經使用了類似於 ${MYVAR} 的標準型別的變數擴充套件。但是 bash 自身也可以執行一些便利的字串截斷。看一下這些例子:


$ MYVAR=foodforthought.jpg
$ echo ${MYVAR##*fo}
rthought.jpg
$ echo ${MYVAR#*fo}
odforthought.jpg


在第一個例子中,輸入了 ${MYVAR##*fo}。它的確切含義是什麼?基本上,在 ${ } 中輸入環境變數名稱,兩個 ##,然後是萬用字元 ("*fo")。然後,bash 取得 MYVAR,找到從字串 "foodforthought.jpg" 開始處開始、且匹配萬用字元 "*fo" 的 最長 子字串,然後將其從字串的開始處截去。剛開始理解時會有些困難,為了感受一下這個特殊的 "##" 選項如何工作,讓我們一步步地看看 bash 如何完成這個擴充套件。首先,它從 "foodforthought.jpg" 的開始處搜尋與 "*fo" 萬用字元匹配的子字串。以下是檢查到的子字串:


f
fo MATCHES *fo
foo
food
foodf
foodfo MATCHES *fo
foodfor
foodfort
foodforth
foodfortho
foodforthou
foodforthoug
foodforthought
foodforthought.j
foodforthought.jp
foodforthought.jpg


在搜尋了匹配的字串之後,可以看到 bash 找到兩個匹配。它選擇最長的匹配,從初始字串的開始處除去,然後返回結果。

上面所示的第二個變數擴充套件形式看起來與第一個相同,但是它只使用一個 "#" -- 並且 bash 執行 幾乎 同樣的過程。它檢視與第一個例子相同的子字串系列,但是 bash 從初始字串除去 最短 的匹配,然後返回結果。所以,一查到 "fo" 子字串,它就從字串中除去 "fo",然後返回 "odforthought.jpg"。

這樣說可能會令人十分困惑,下面以一簡單方式記住這個功能。當搜尋最長匹配時,使用 ##(因為 ## 比 # 長)。當搜尋最短匹配時,使用 #。看,不難記吧!等一下,怎樣記住應該使用 '#' 字元來從字串開始部分除去?很簡單!注意到了嗎:在美國鍵盤上,shift-4 是 "$",它是 bash 變數擴充套件字元。在鍵盤上,緊靠 "$" 左邊的是 "#"。這樣,可以看到:"#" 位於 "$" 的“開始處”,因此(根據我們的記憶法),"#" 從字串的開始處除去字元。您可能要問:如何從字串末尾除去字元。如果猜到我們使用美國鍵盤上緊靠 "$" 右邊 的字元 ("%),那就猜對了。這裡有一些簡單的例子,解釋如何截去字串的末尾部分:


$ MYFOO="chickensoup.tar.gz"
$ echo ${MYFOO%%.*}
chickensoup
$ echo ${MYFOO%.*}
chickensoup.tar


正如您所見,除了將匹配萬用字元從字串末尾除去之外,% 和 %% 變數擴充套件選項與 # 和 ## 的工作方式相同。請注意:如果要從末尾除去特定子字串,不必使用 "*" 字元:


MYFOOD="chickensoup"
$ echo ${MYFOOD%%soup}
chicken


在此例中,使用 "%%" 或 "%" 並不重要,因為只能有一個匹配。還要記住:如果忘記了應該使用 "#" 還是 "%",則看一下鍵盤上的 3、4 和 5 鍵,然後猜出來。

可以根據特定字元偏移和長度,使用另一種形式的變數擴充套件,來選擇特定子字串。試著在 bash 中輸入以下行:


$ EXCLAIM=cowabunga
$ echo ${EXCLAIM:0:3}
cow
$ echo ${EXCLAIM:3:7}
abunga


這種形式的字串截斷非常簡便,只需用冒號分開來指定起始字元和子字串長度。


回頁首

應用字串截斷

現在我們已經學習了所有截斷字串的知識,下面寫一個簡單短小的 shell 指令碼。我們的指令碼將接受一個檔案作為自變數,然後列印:該檔案是否是一個 tar 檔案。要確定它是否是 tar 檔案,將在檔案末尾查詢模式 ".tar"。如下所示:


mytar.sh -- 一個簡單的指令碼

#!/bin/bash
if [ "${1##*.}" = "tar" ]
then
echo This appears to be a tarball.
else
echo At first glance, this does not appear to be a tarball.
fi


要執行此指令碼,將它輸入到檔案 mytar.sh 中,然後輸入 "chmod 755 mytar.sh",生成可執行檔案。然後,如下做一下 tar 檔案試驗:


$ ./mytar.sh thisfile.tar
This appears to be a tarball.
$ ./mytar.sh thatfile.gz
At first glance, this does not appear to be a tarball.


好,成功執行,但是不太實用。在使它更實用之前,先看一下上面使用的 "if" 語句。語句中使用了一個布林表示式。在 bash 中,"=" 比較運算子檢查字串是否相等。在 bash 中,所有布林表示式都用方括號括起。但是布林表示式實際上測試什麼?讓我們看一下左邊。根據前面所學的字串截斷知識,"${1##*.}" 將從環境變數 "1" 包含的字串開始部分除去最長的 "*." 匹配,並返回結果。這將返回檔案中最後一個 "." 之後的所有部分。顯然,如果檔案以 ".tar" 結束,結果將是 "tar",條件也為真。

您可能會想:開始處的 "1" 環境變數是什麼。很簡單 -- $1 是傳給指令碼的第一個命令列自變數,$2 是第二個,以此類推。好,已經回顧了功能,下面來初探 "if" 語句。


回頁首

If 語句

與大多數語言一樣,bash 有自己的條件形式。在使用時,要遵循以上格式;即,將 "if" 和 "then" 放在不同行,並使 "else" 和結束處必需的 "fi" 與它們水平對齊。這將使程式碼易於閱讀和除錯。除了 "if,else" 形式之外,還有其它形式的 "if" 語句:


if [ condition ]
then
action
fi


只有當 condition 為真時,該語句才執行操作,否則不執行操作,並繼續執行 "fi" 之後的任何行。


if [ condition ]
then
action
elif [ condition2 ]
then
action2
.
.
.
elif [ condition3 ]
then
else
actionx
fi


以上 "elif" 形式將連續測試每個條件,並執行符合第一個 真 條件的操作。如果沒有條件為真,則將執行 "else" 操作,如果有一個條件為真,則繼續執行整個 "if,elif,else" 語句之後的行。


回頁首

下一次

我們已經學習了最基本的 bash 功能,現在要加快腳步,準備編寫一些實際指令碼。在下一篇中,將講述迴圈概念、函式、名稱空間和其它重要主題。然後,將準備好編寫一些更復雜的指令碼。在第三篇中,將重點講述一些非常複雜的指令碼和功能,以及幾個 bash 指令碼設計選項。再見!


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Bash 例項,第 2 部分
更多的 bash 基本程式設計


文件選項
將此頁作為電子郵件傳送

最新推薦
Java 應用開發源動力 - 下載免費軟體,快速啟動開發


級別: 初級

Daniel Robbins, 總裁兼 CEO, Gentoo Technologies, Inc.


2000 年 4 月 01 日

在前一篇 bash 的介紹性文章中,Daniel Robbins 為您講解了指令碼語言的一些基本元素和使用 bash 的原因。在本文(即第二部分)中,Daniel 繼續前一篇的內容,並講解條件 (if-then) 語句、迴圈和更多的 bash 基本結構。
我們先看一下處理命令列自變數的簡單技巧,然後再看看 bash 基本程式設計結構。

接收自變數

在 介紹性文章 中的樣本程式中,我們使用環境變數 "$1" 來引用第一個命令列自變數。類似地,可以使用 "$2"、"$3" 等來引用傳遞給指令碼的第二和第三個自變數。這裡有一個例子:


#!/usr/bin/env bash

echo name of script is $0
echo first argument is $1
echo second argument is $2
echo seventeenth argument is $17
echo number of arguments is $#


除以下兩個細節之外,此例無需說明。第一,"$0" 將擴充套件成從命令列呼叫的指令碼名稱,"$#" 將擴充套件成傳遞給指令碼的自變數數目。試驗以上指令碼,通過傳遞不同型別的命令列自變數來了解其工作原理。

有時需要一次引用 所有 命令列自變數。針對這種用途,bash 實現了變數 "$@",它擴充套件成所有用空格分開的命令列引數。在本文稍後的 "for" 迴圈部分中,您將看到使用該變數的例子。


回頁首

Bash 程式設計結構

如果您曾用過如 C、Pascal、Python 或 Perl 那樣的過程語言程式設計,則一定熟悉 "if" 語句和 "for" 迴圈那樣的標準程式設計結構。對於這些標準結構的大多數,Bash 有自己的版本。在下幾節中,將介紹幾種 bash 結構,並演示這些結構和您已經熟悉的其它程式語言中結構的差異。如果以前程式設計不多,也不必擔心。我提供了足夠的資訊和示例,使您可以跟上本文的進度。


回頁首

方便的條件語句

如果您曾用 C 編寫過與檔案相關的程式碼,則應該知道:要比較特定檔案是否比另一個檔案新需要大量工作。那是因為 C 沒有任何內建語法來進行這種比較,必須使用兩個 stat() 呼叫和兩個 stat 結構來進行手工比較。相反,bash 內建了標準檔案比較運算子,因此,確定“/tmp/myfile 是否可讀”與檢視“$myvar 是否大於 4”一樣容易。

下表列出最常用的 bash 比較運算子。同時還有如何正確使用每一選項的示例。示例要跟在 "if" 之後。例如:


if [ -z "$myvar" ]
then
echo "myvar is not defined"
fi


運算子 描述 示例
檔案比較運算子
-e filename 如果 filename存在,則為真 [ -e /var/log/syslog ]
-d filename 如果 filename為目錄,則為真 [ -d /tmp/mydir ]
-f filename 如果 filename為常規檔案,則為真 [ -f /usr/bin/grep ]
-L filename 如果 filename為符號連結,則為真 [ -L /usr/bin/grep ]
-r filename 如果 filename可讀,則為真 [ -r /var/log/syslog ]
-w filename 如果 filename可寫,則為真 [ -w /var/mytmp.txt ]
-x filename 如果 filename可執行,則為真 [ -L /usr/bin/grep ]
filename1-nt filename2 如果 filename1比 filename2新,則為真 [ /tmp/install/etc/services -nt /etc/services ]
filename1-ot filename2 如果 filename1比 filename2舊,則為真 [ /boot/bzImage -ot arch/i386/boot/bzImage ]
字串比較運算子 (請注意引號的使用,這是防止空格擾亂程式碼的好方法)
-z string 如果 string長度為零,則為真 [ -z "$myvar" ]
-n string 如果 string長度非零,則為真 [ -n "$myvar" ]
string1= string2 如果 string1與 string2相同,則為真 [ "$myvar" = "one two three" ]
string1!= string2 如果 string1與 string2不同,則為真 [ "$myvar" != "one two three" ]
算術比較運算子
num1-eq num2 等於 [ 3 -eq $mynum ]
num1-ne num2 不等於 [ 3 -ne $mynum ]
num1-lt num2 小於 [ 3 -lt $mynum ]
num1-le num2 小於或等於 [ 3 -le $mynum ]
num1-gt num2 大於 [ 3 -gt $mynum ]
num1-ge num2 大於或等於 [ 3 -ge $mynum ]


有時,有幾種不同方法來進行特定比較。例如,以下兩個程式碼段的功能相同:


if [ "$myvar" -eq 3 ]
then
echo "myvar equals 3"
fi


if [ "$myvar" = "3" ]
then
echo "myvar equals 3"
fi


上面兩個比較執行相同的功能,但是第一個使用算術比較運算子,而第二個使用字串比較運算子。


回頁首

字串比較說明

大多數時候,雖然可以不使用括起字串和字串變數的雙引號,但這並不是好主意。為什麼呢?因為如果環境變數中恰巧有一個空格或製表鍵,bash 將無法分辨,從而無法正常工作。這裡有一個錯誤的比較示例:


if [ $myvar = "foo bar oni" ]
then
echo "yes"
fi


在上例中,如果 myvar 等於 "foo",則程式碼將按預想工作,不進行列印。但是,如果 myvar 等於 "foo bar oni",則程式碼將因以下錯誤失敗:


[: too many arguments


在這種情況下,"$myvar"(等於 "foo bar oni")中的空格迷惑了 bash。bash 擴充套件 "$myvar" 之後,程式碼如下:


[ foo bar oni = "foo bar oni" ]


因為環境變數沒放在雙引號中,所以 bash 認為方括號中的自變數過多。可以用雙引號將字串自變數括起來消除該問題。請記住,如果養成將所有字串自變數用雙引號括起的習慣,將除去很多類似的程式設計錯誤。"foo bar oni" 比較 應該寫成:


if [ "$myvar" = "foo bar oni" ]
then
echo "yes"
fi

更多引用細節

如果要擴充套件環境變數,則必須將它們用 雙引號、而不是單引號括起。單引號 禁用 變數(和歷史)擴充套件。


以上程式碼將按預想工作,而不會有任何令人不快的意外出現。


回頁首

迴圈結構:"for"

好了,已經講了條件語句,下面該探索 bash 迴圈結構了。我們將從標準的 "for" 迴圈開始。這裡有一個簡單的例子:



#!/usr/bin/env bash

for x in one two three four
do
echo number $x
done


輸出:

number one
number two
number three
number four


發生了什麼?"for" 迴圈中的 "for x" 部分定義了一個名為 "$x" 的新環境變數(也稱為迴圈控制變數),它的值被依次設定為 "one"、"two"、"three" 和 "four"。每一次賦值之後,執行一次迴圈體("do" 和 "done" 之間的程式碼)。在迴圈體內,象其它環境變數一樣,使用標準的變數擴充套件語法來引用迴圈控制變數 "$x"。還要注意,"for" 迴圈總是接收 "in" 語句之後的某種型別的字列表。在本例中,指定了四個英語單詞,但是字列表也可以引用磁碟上的檔案,甚至檔案萬用字元。看看下面的例子,該例演示如何使用標準 shell 萬用字元:


#!/usr/bin/env bash

for myfile in /etc/r*
do
if [ -d "$myfile" ]
then
echo "$myfile (dir)"
else
echo "$myfile"
fi
done


輸出:

/etc/rc.d (dir)
/etc/resolv.conf
/etc/resolv.conf~
/etc/rpc


以上程式碼列出在 /etc 中每個以 "r" 開頭的檔案。要做到這點,bash 在執行迴圈之前首先取得萬用字元 /etc/r*,然後擴充套件它,用字串 /etc/rc.d /etc/resolv.conf /etc/resolv.conf~ /etc/rpc 替換。一旦進入迴圈,根據 myfile 是否為目錄,"-d" 條件運算子用來執行兩個不同操作。如果是目錄,則將 "(dir)" 附加到輸出行。

還可以在字列表中使用多個萬用字元、甚至是環境變數:



for x in /etc/r--? /var/lo* /home/drobbins/mystuff/* /tmp/${MYPATH}/*
do
cp $x /mnt/mydir
done


Bash 將在所有正確位置上執行萬用字元和環境變數擴充套件,並可能建立一個非常長的字列表。

雖然所有萬用字元擴充套件示例使用了 絕對路徑,但也可以使用相對路徑,如下所示:



for x in ../* mystuff/*
do
echo $x is a silly file
done


在上例中,bash 相對於當前工作目錄執行萬用字元擴充套件,就象在命令列中使用相對路徑一樣。研究一下萬用字元擴充套件。您將注意到,如果在萬用字元中使用絕對路徑,bash 將萬用字元擴充套件成一個絕對路徑列表。否則,bash 將在後面的字列表中使用相對路徑。如果只引用當前工作目錄中的檔案(例如,如果輸入 "for x in *"),則產生的檔案列表將沒有路徑資訊的字首。請記住,可以使用 "basename" 可執行程式來除去前面的路徑資訊,如下所示:



for x in /var/log/*
do
echo `basename $x` is a file living in /var/log
done


當然,在指令碼的命令列自變數上執行迴圈通常很方便。這裡有一個如何使用本文開始提到的 "$@" 變數的例子:



#!/usr/bin/env bash

for thing in "$@"
do
echo you typed ${thing}.
done


輸出:

$ allargs hello there you silly
you typed hello.
you typed there.
you typed you.
you typed silly.


回頁首

Shell 算術

在學習另一型別的迴圈結構之前,最好先熟悉如何執行 shell 算術。是的,確實如此:可以使用 shell 結構來執行簡單的整數運算。只需將特定的算術表示式用 "$((" 和 "))" 括起,bash 就可以計算表示式。這裡有一些例子:


$ echo $(( 100 / 3 ))
33
$ myvar="56"
$ echo $(( $myvar + 12 ))
68
$ echo $(( $myvar - $myvar ))
0 $ myvar=$(( $myvar + 1 ))
$ echo $myvar
57


回頁首

更多的迴圈結構:"while" 和 "until"

只要特定條件為真,"while" 語句就會執行,其格式如下:


while [ condition ]
do
statements
done


通常使用 "While" 語句來迴圈一定次數,比如,下例將迴圈 10 次:


myvar=0
while [ $myvar -ne 10 ]
do
echo $myvar
myvar=$(( $myvar + 1 ))
done


可以看到,上例使用了算術表示式來使條件最終為假,並導致迴圈終止。

"Until" 語句提供了與 "while" 語句相反的功能:只要特定條件為 假 ,它們就重複。下面是一個與前面的 "while" 迴圈具有同等功能的 "until" 迴圈:


myvar=0
until [ $myvar -eq 10 ]
do
echo $myvar
myvar=$(( $myvar + 1 ))
done


回頁首

Case 語句

Case 語句是另一種便利的條件結構。這裡有一個示例片段:



case "${x##*.}" in
gz)
gzunpack ${SROOT}/${x}
;;
bz2)
bz2unpack ${SROOT}/${x}
;;
*)
echo "Archive format not recognized."
exit
;;
esac


在上例中,bash 首先擴充套件 "${x##*.}"。在程式碼中,"$x" 是檔案的名稱,"${x##.*}" 除去檔案中最後句點後文字之外的所有文字。然後,bash 將產生的字串與 ")" 左邊列出的值做比較。在本例中,"${x##.*}" 先與 "gz" 比較,然後是 "bz2",最後是 "*"。如果 "${x##.*}" 與這些字串或模式中的任何一個匹配,則執行緊接 ")" 之後的行,直到 ";;" 為止,然後 bash 繼續執行結束符 "esac" 之後的行。如果不匹配任何模式或字串,則不執行任何程式碼行,在這個特殊的程式碼片段中,至少要執行一個程式碼塊,因為任何不與 "gz" 或 "bz2" 匹配的字串都將與 "*" 模式匹配。


回頁首

函式與名稱空間

在 bash 中,甚至可以定義與其它過程語言(如 Pascal 和 C)類似的函式。在 bash 中,函式甚至可以使用與指令碼接收命令列自變數類似的方式來接收自變數。讓我們看一下樣本函式定義,然後再從那裡繼續:



tarview() {
echo -n "Displaying contents of $1 "
if [ ${1##*.} = tar ]
then
echo "(uncompressed tar)"
tar tvf $1
elif [ ${1##*.} = gz ]
then
echo "(gzip-compressed tar)"
tar tzvf $1
elif [ ${1##*.} = bz2 ]
then
echo "(bzip2-compressed tar)"
cat $1 | bzip2 -d | tar tvf -
fi
}

另一種情況

可以使用 "case" 語句來編寫上面的程式碼。您知道如何編寫嗎?


我們在上面定義了一個名為 "tarview" 的函式,它接收一個自變數,即某種型別的 tar 檔案。在執行該函式時,它確定自變數是哪種 tar 檔案型別(未壓縮的、gzip 壓縮的或 bzip2 壓縮的),列印一行資訊性訊息,然後顯示 tar 檔案的內容。應該如下呼叫上面的函式(在輸入、貼上或找到該函式後,從指令碼或命令列呼叫它):


$ tarview shorten.tar.gz
Displaying contents of shorten.tar.gz (gzip-compressed tar)
drwxr-xr-x ajr/abbot 0 1999-02-27 16:17 shorten-2.3a/
-rw-r--r-- ajr/abbot 1143 1997-09-04 04:06 shorten-2.3a/Makefile
-rw-r--r-- ajr/abbot 1199 1996-02-04 12:24 shorten-2.3a/INSTALL
-rw-r--r-- ajr/abbot 839 1996-05-29 00:19 shorten-2.3a/LICENSE
....

互動地使用它們

別忘了,可以將函式(如上面的函式)放在 ~/.bashrc 或 ~/.bash_profile 中,以便在 bash 中隨時使用它們。


如您所見,可以使用與引用命令列自變數同樣的機制來在函式定義內部引用自變數。另外,將把 "$#" 巨集擴充套件成包含自變數的數目。唯一可能不完全相同的是變數 "$0",它將擴充套件成字串 "bash"(如果從 shell 互動執行函式)或呼叫函式的指令碼名稱。


回頁首

名稱空間

經常需要在函式中建立環境變數。雖然有可能,但是還有一個技術細節應該瞭解。在大多數編譯語言(如 C)中,當在函式內部建立變數時,變數被放置在單獨的區域性名稱空間中。因此,如果在 C 中定義一個名為 myfunction 的函式,並在該函式中定義一個名為 "x" 的自變數,則任何名為 "x" 的全域性變數(函式之外的變數)將不受它的印象,從而消除了負作用。

在 C 中是這樣,但在 bash 中卻不是。在 bash 中,每當在函式內部建立環境變數,就將其新增到 全域性名稱空間。這意味著,該變數將重寫函式之外的全域性變數,並在函式退出之後繼續存在:


#!/usr/bin/env bash

myvar="hello"

myfunc() {

myvar="one two three"
for x in $myvar
do
echo $x
done
}

myfunc

echo $myvar $x


執行此指令碼時,它將輸出 "one two three three",這顯示了在函式中定義的 "$myvar" 如何影響全域性變數 "$myvar",以及迴圈控制變數 "$x" 如何在函式退出之後繼續存在(如果 "$x" 全域性變數存在,也將受到影響)。

在這個簡單的例子中,很容易找到該錯誤,並通過使用其它變數名來改正錯誤。但這不是正確的方法,解決此問題的最好方法是通過使用 "local" 命令,在一開始就預防影響全域性變數的可能性。當使用 "local" 在函式內部建立變數時,將把它們放在 區域性名稱空間中,並且不會影響任何全域性變數。這裡演示瞭如何實現上述程式碼,以便不重寫全域性變數:



#!/usr/bin/env bash

myvar="hello"

myfunc() {
local x
local myvar="one two three"
for x in $myvar
do
echo $x
done
}

myfunc

echo $myvar $x


此函式將輸出 "hello" -- 不重寫全域性變數 "$myvar","$x" 在 myfunc 之外不繼續存在。在函式的第一行,我們建立了以後要使用的區域性變數 x,而在第二個例子 (local myvar="one two three"") 中,我們建立了區域性變數 myvar, 同時 為其賦值。在將迴圈控制變數定義為區域性變數時,使用第一種形式很方便,因為不允許說:"for local x in $myvar"。此函式不影響任何全域性變數,鼓勵您用這種方式設計所有的函式。只有在明確希望要修改全域性變數時,才 不應該使用 "local"。


回頁首

結束語

我們已經學習了最基本的 bash 功能,現在要看一下如何基於 bash 開發整個應用程式。下一部分正要講到。再見!


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/756652/viewspace-242125/,如需轉載,請註明出處,否則將追究法律責任。

相關文章