您的位置:首頁技術文章
文章詳情頁

UNIX 新手指南: 一些很好的 Shell 訣竅

瀏覽:23日期:2024-06-12 18:14:42

當編寫 Shell 程序時,您通常會遇到一些特殊的情況,希望采用自動方式處理。本教程包括一些關于此類情況的 Bourne Shell 腳本示例。這些情況包括字符串的進制轉換(十進制到十六進制、十六進制到十進制、十進制到八進制,等等)、在管道循環中讀取鍵盤、Subshell 執行、內聯輸入、為目錄中的每個文件執行一次命令,以及使用多種方法構造連續循環。本系列文章的第 4 部分總結了一批執行有用功能的 Shell 單命令行程序。

開始之前

了解本教程中包含的內容以及如何最好地利用本教程。

關于本系列

本系列教程主要針對新用戶撰寫,簡要介紹 Unix® 基本概念。本系列教程的前三篇文章站在擁有 Microsoft® Windows® 背景的新用戶的角度重溫了一遍 UNIX 系統,講述了文件系統和常用命令,介紹了 vi(最常見的 UNIX 編輯器),并且通過使用 grep、sed 和 awk 工具簡要介紹了篩選器和正則表達式。

關于本教程

本教程介紹了一套新用戶易于掌握的訣竅和技巧。說明在特定情況下,如何使用在 Bourne Shell 中編寫的小腳本自動執行操作,包括自動執行進制轉換、讀取鍵盤輸入、在 Subshell 中執行命令、為目錄中的所有文件執行相同命令,以及多種形式的循環。本教程最后以一套實用的 Shell 單命令行程序作為結束。

目標

本教程的目標是向新用戶介紹如何使用和實現許多在各種級別上提供自動化操作的 Shell 方法。本教程通過提供針對特定情況的訣竅和技巧來說明這些方法,并且提供適用于常見任務的 Shell 單命令行程序的概要性介紹。

先決條件

本教程面向相對不熟悉 UNIX 的用戶。唯一的先決條件是了解 UNIX 文件系統的基本知識和操作命令、命令行本身,以及能夠使用類似 vi 的編輯器編寫文本文件。本系列教程的前面部分對這些概念作了全面說明。

系統要求

您需要在帶有 Bourne 兼容 Shell 環境(例如 bash)的 UNIX 系統上擁有用戶級訪問權限。這是本教程唯一的系統要求。

Shell 命令執行

學習 Shell 腳本的最佳方法是通過示例。對于您要在腳本中執行的任何命令都可以在命令行上立即嘗試,這也是本教程通篇提供大量實踐示例的原因所在。例如,echo 命令將一行文本寫入到標準輸出。(許多 Shell 以內置命令形式提供其自己版本的 echo 命令,包括 IBM AIX® 的 Bourne Shell 實現。如果這也是您的現實情況,那么當您運行 echo 時,實際上正在運行您的 Shell 版本的命令。)

引用

嘗試在使用 echo 輸出短消息時加引號:

$ echo "Hello, world"Hello, world

Shell 引用(無論在命令行還是在腳本中加注)是一種將字符串傳遞給 Shell 的方法,可以避免對字符串中可能包含的任何特殊元字符產生混淆。當字符串包含一個以上的單詞或者段落包含空格字符時使用引用。如果單個字符恰好是 Shell 元字符,并且您想去除它的特殊含義,就可以在兩邊加上引號,例如,當您要傳遞一個美元符號 ($) 作為字面上的美元符號字符而不是作為變量名前的特殊元字符時。

在引用的文本內部發生各種擴展。例如,在雙引號括起來的文本中,變量被展開為它們的值,而單引號括起來的文本內部引用的變量名則不展開。

有三種重要的引用類型需要了解:

通過在前面加反斜杠 () 引用單個字符。這樣只會傳替字符的字面含義,而非它可能包含的任何特殊含義,比如空格符或 Shell 元字符。例如,使用 * 引用一個星號 (*),它是 Shell 元字符。要引用真正的反斜杠字符,可以使用 。

通過在文本字符串兩邊加雙引號 (") 來傳遞擴展的引用。美元符號 ($) 和單引號 (') 字符會保留其自身含義。因此,和其他字符一起在引用中出現的任何變量名都會被它們的值所替代。新行或特定字符 ($`") 前的反斜杠被移除,但是引用的字符會被傳遞。

使用單引號 (') 將文本括起來以傳遞文本字符串的字面引用,所有的變量名、元字符等都作為文字字符,而不它們的含義或值來傳遞。

請注意在不同的 Shell 中引用的確切規則會有所區別。參考您所使用的特殊 Shell 的 man 頁面來了解準確規則。

分配一個變量,然后嘗試使用各種引用格式輸出該變量,如清單 1 中所示。

清單 1. 使用 echo 演示 Shell 變量引用格式

$ myvar = "Hello, world"$ echo $myvarHello, world$ echo "$myvar"Hello, world$ echo '$myvar'$myvar$ echo $myvar$myvar$ echo '$myvar''Hello, world'$ echo "'$myvar'"'Hello, world'$ echo '"$myvar"'"$myvar"$ echo "$myvar""Hello, world"

注意解釋變量的方式取決于所使用的引用格式。

注釋

在 Shell 中,以井號 (#) 開始一個注釋行。井號及其后面跟隨的同一行的所有內容都被忽略。嘗試輸入幾行夾雜注釋的文本,如清單 2 中所示:

清單 2. 在 Shell 中使用注釋

$ # a comment does nothing$ echo "Hello, world" # This text is ignoredHello, world$ echo # This will not output$ echo 'But a hash (#) can be quoted'But a hash (#) can be quoted$ echo "# Even in double quotes"# Even in double quotes$

創建 Shell 腳本

正如您所看到的,您可以直接在命令行測試這些 Shell 編程結構。但是,當您完成了單行命令的學習并且真正開始構建更長的程序時,您需要將程序寫入稱為腳本的文件。腳本 是一個設置了可執行位的文本文件,并且包含由 Shell 語言命令組成的程序。UNIX Shell 是一種解釋性語言,這意味著它的程序不經過編譯,而是由解釋器讀取,解釋器本身是 Shell 可執行程序,比如 /bin/sh、/bin/bsh 或 /bin/bash。

Shell 腳本的第一行通常都是相同的:

#!/bin/sh

這是 Shell 自己使用的一種特殊注釋,用于確定文件的語言或目錄。感嘆號在 UNIX 和排版術語中常常被稱為 bang,后面跟隨的路徑名告訴 Shell 應該使用來執行該文件的解釋器。在本例中是 /bin/sh,它在許多系統中代表 Bourne Shell 可執行程序本身。舉例來說,特別為 Korn Shell 編寫的腳本應該以 #!/usr/bin/ksh 開始,正如 Ruby 腳本將以 #!/usr/bin/ruby 開始。安裝 bash 之后,/bin/sh 通常是到 bash 二進制程序的符號鏈接。并且考慮到兼容性,使用 /bin/sh 比使用 /bin/bash 更可取。在一些系統中,比如 IBM AIX 5L™,Bourne Shell 可執行程序的名稱是 bsh,并且位于 /usr/bin/bsh。

清單 3 提供了 Shell 腳本的簡短示例。

清單 3. Shell 腳本示例

#!/bin/sh# This is a shell scriptmessage = "Hello, world!"echo "The message is '"$message"'"

按照本系列教程前面文章中的說明,使用 vi 編輯器鍵入該腳本并保存到名為 myscript 的文件中(請參見參考資料部分)。然后使用 chmod 設置該文件的執行權限,使該文件可以執行:

$ chmod u+x myscript

此命令使該文件只能由您執行。如果希望系統中的所有用戶都能執行該文件,那么您還可以為所有用戶設置執行權限:

$ chmod a+x myscript

現在您可以運行該腳本。給出該文件的名稱和相對于當前工作目錄的路徑,在路徑中使用一個點字符 (.) 來表示:

$ ./myscriptThe message is 'Hello, world!'$

Shell 變量 PATH 包含一組以冒號分隔的目錄。它就是您的路徑,Shell 總是會“看到這些目錄中的所有文件。UNIX Path 的目的是為了便于運行二進制文件。這就是為什么您只需要鍵入命令的基本文件名,比如 ls 和 echo,而不用提供它們的完整或相對路徑名。如果您將腳本移動到 Path 中的目錄,那么只需鍵入它的名字就可以運行。具體的 Path 取決于您的 UNIX 實現和本地設置,但 Path 中的目錄通常包括 /bin、/usr/bin 和 /usr/local/bin。

一些用戶對它們的 Shell 進行配置,從而使 PATH 變量包括當前的工作目錄,這在 Path 中以點字符 (".") 表示。如此一來,要在當前目錄下運行腳本,只需要鍵入它的名稱,不需要指出相對目錄。,Shell 按給定的順序搜索 Path中的目錄,從而避免中木馬或發生異常情況,一種極其不明智的做法是把當前工作目錄放在 Path 的末尾。

要查看您的 Path,可以使用 echo 顯示 PATH 變量的內容,如清單 4 所示。

清單 4. 更改 PATH

$ echo $PATH/usr/local/bin:/usr/bin:/bin:/usr/bin/X11$ myscriptmyscript: command not found$ PATH = $PATH":."$ echo $PATH/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:.$ myscriptThe message is 'Hello, world!'$

在解釋器名稱的后面可以附加特殊選項或標志,比如 /usr/bin/bsh -n,這用于調試目的。連字符關閉選項,加號則打開選項。特殊的內置環境變量 -(一個連字符)包含當前 Shell 的完整選項列表。

嘗試在您當前的交互式 Shell 中設置了哪些選項。通過使用 echo 顯示 - 變量的內容來完成這項任務:

$ echo $-himBH$

參考您使用的 Shell 的 man 頁面來獲取當前的標志和選項列表。表 1 提供了 AIX® 上的 Bourne Shell 的常用標志列表,以及對每種標志作用的簡要說明。

表 1. AIX Bourne Shell 的常用選項

標志描述-a 導出所有已分配值的變量。-c Variable 執行從變量 中讀取的命令。-e 當命令滿足以下條件之一時立即退出:命令退出時返回比 0 大的值;命令不是 while、until 或 if 結構的一部分;命令不經過 AND 或 OR 檢測;或者命令不是管道前加感嘆號。-f 禁用所有文件名替換。-h 定義函數時,定位和記住函數內部調用的所有命令。-i 指定交互式 Shell。-k 將所有關鍵字 都放入命令的環境。-n 讀取命令,但是不執行它們。-r 調用受限制的 Shell。-s 從標準輸入讀取命令,然后將輸出寫入標準錯誤(不包括 Shell 內置命令的輸出)。-t 讀取并執行單個命令,然后退出。-u 在腳本中,將所有未定義 變量視為錯誤。當嘗試變量替換時退出。-v 當讀取輸入行時將其顯示出來。-x 在執行命令之前顯示其完整命令(包括所有的參數和選項)。

Shell 運算和進制轉換

Shell 提供大量的基本運算操作,在腳本中非常有用。Shell 對您提供的算術表達式求值,執行運算展開式,此時使用得出的結果替換表達式。以下面的格式提供運算表達式:

$(( expression ))

您可以使用 echo 在命令行顯示運算展開式的結果,了解其工作情況?,F在嘗試清單 5 所顯示的結果。

清單 5. Bourne Shell 中的運算展開式

$ echo $((10+40))50$ echo $((5*(3+3)))30

您還可以將展開式分配給變量。嘗試清單 6 所顯示的結果。

清單 6. 將運算展開式分配給 Shell 變量

$ myvar = 10$ echo $myvar10$ echo $(($myvar-2))8$ myvar = $(($myvar+5))$ echo $myvar15$ result = $(($myvar-10))$ echo $result5$

表 2 列出了在大多數 Bourne 以及與 Bourne 兼容的 Shell中可以使用的運算符。正如上面第二個示例,使用圓括號括起來的語句有更高的優先級。實際上,Shell 算術優先級通常根據 C 語言的規則來確定。

表 2. Shell 條件表達式

運算符描述+ 加- 減* 乘/ 除% 求余< 小于(1 代表真,0 代表假)<= 小于等于(1 代表真,0 代表假)> 大于(1 代表真,0 代表假)>= 大于等于(1 代表真,0 代表假)<< 按位向左移位:將給定的整數或第一個表達式向左移動第二個表達式表示的位數>> 按位向右移位:將給定的整數或第一個表達式向右移動第二個表達式表示的位數

使用 Shell 運算進行進制轉換

假定在您的腳本中有一些數字,您需要以另外的進制處理這些數字。使用 Shell 運算可以很容易地自動實現這類轉換。一種情況是使用 Shell 運算把一個數字從給定的進制轉換位十進制。如果數字以運算展開式的形式提供,那么假定它帶有十進制符號,除非 它前面帶有 0(這種情況假定是八進制)或 0x(這種情況假定是十六進制)。鍵入以下內容以得到一些八進制和十六進制值的十進制輸出:

$ echo $((013))$ echo $((0xA4))

您還可以使用以下格式指定 2 到 64 之間的任意進制:

$((BASE#NUMBER))

通過在 Shell 提示符后鍵入清單 7 中所示的行,嘗試將二進制、八進制、十六進制以及其他進制的數轉換為十進制。

清單 7. 在 Shell 中將任意進制的數以十進制輸出

echo $((2#1101010))echo $((8#377))echo $((16#D8))echo $((12#10))echo $((36#ZZYY))

使用 bc 進行進制轉換

在 Shell 中進行進制轉換的另一個訣竅是使用 bc,它是一種任意精度運算語言,大多數 UNIX 安裝程序都提供。因為它允許您指定輸出進制,所以當您需要以十進制以外的進制輸出時,這是一種很好的技術。

bc 的特殊變量 ibase 和 obase 分別包含用于輸入和輸出的進制的值。缺省情況下,都被設置為 10。要執行進制轉換,需要改變其中的一個或兩個值,然后提供一個數字。立即嘗試,如清單 8 中所示。

清單 8. 使用 bc 執行進制轉換

$ bc -ql1010obase=1610Aibase=2102Control-D$

要快速執行進制轉換,可以聯合使用 bc 和 echo形成快捷的單命令行程序,將給定的值通過管道傳輸給 bc。鍵入清單 9 中顯示的內容。

清單 9. Shell 單命令行 bc 程序

$ echo 'obase=16; 47' | bc2F$ echo 'obase=10; ibase=16; A03' | bc2563$

警告:當您設置 bc 的輸入進制以后,輸入 bc 的所有數字都使用該進制,包括您提供用于設置輸出進制的數字。因此最好先設置輸出進制,否則可能會產生意想不到的結果,如清單 10 中所示。

清單 10. 設置輸入和輸出進制的先后順序的重要性

$ echo 'ibase=16; obase=10; A' | bcA$ echo 'ibase=16; obase=A; A' | bc10$

內聯輸入

盡管 echo 通過管道將內容傳遞給交互式命令(比如 bc)可以生成快捷的單命令行程序,但是它對于多行輸入并不適用,比如可能用到實際文件中的內容。但是另外一種有用的方法可以完成這個任務。Shell 有一種工具稱為 here documents 或內聯輸入,這是一種動態構建文件的非常好的方法,比如用于腳本內部,并且將該文件的內容重定向到一個命令。

使用 Shell << 操作符來指定一個 here document,然后在同一行的后面跟上一個限定字符串,該字符串標記輸入的結束,并且您可以選擇任何文本,只要是不包含空格字符的單個詞都可以。其后跟隨構成您的輸入文件的行,然后以獨占一行的限定字符串結束輸入,在它的前面或后面不能有任何文本,否則該行將被視為輸入的一部分。使用 cat 進行嘗試,如清單 11 中所示。

清單 11. 編寫 here document

$ cat << END> END of input text> ENDspace> This is still not the END> ENDING SOON> THE END> ENDEND of input textENDThis is still not the ENDENDING SOONTHE END$

限定字符串(本例中是 END)可以出現在輸入的任何地方,只有當它以獨占一行并且不含空格或其他字符的形式出現時,才表示輸入的結束。

腳本中的內聯輸入

在腳本中經常使用內聯輸入將使用信息輸出到標準輸出。這通常通過將 here document 發送給 cat 來完成,如清單 12 中的腳本所示。使用 vi 輸入該腳本并保存到名為 baseconv 的文件中,并且將該文件設置為可執行文件(請參見創建 Shell 腳本部分)。

清單 12. 使用 here document 提供 Shell 腳本使用信息

#!/bin/shcat << EOFbaseconv is a program to convert a number from one base to another.Usage: baseconv [options]Options:-iBASE input base-oBASE output base-hdisplay this messageFor more information, consult the baseconv man page.EOF

當執行該腳本時,here document 的內容被發送到(使用 cat)標準輸出。立即嘗試,如清單 13 中所示。

清單 13. 從 here document 輸出 Shell 腳本使用信息

$ baseconvbaseconv is a program to convert a number from one base to another.Usage: baseconv [options]Options:-iBASE input base-oBASE output base-hdisplay this messageFor more information, consult the baseconv man page.$

此外,Bourne Shell 的大多數實現允許出現使用可選的連字符重定向的內聯輸入??蛇x的連字符將所有的前導 Tab 字符從所有輸入行的前面去掉,也包括包含限定字符串的行。這對于您希望讓編寫的腳本保持當前縮進時會有幫助。由于內聯輸入通常逐字讀取,并且限定字符串必須在行的開始處給出,因此輸入將打亂您的當前縮進并使腳本看起來不雅觀。因此,您可以重寫清單 12 中的腳本,使其與清單 14 一致,而輸出不會改變。

清單 14. 帶前導縮進的 Shell 腳本 here document

#!/bin/shcat <<- EOFbaseconv is a program to convert a number from one base to another.Usage: baseconv [options]Options:-iBASE input base-oBASE output base-hdisplay this messageFor more information, consult the baseconv man page.EOF

在命令行使用內聯輸入

在命令行中,使用調用交互式程序的單命令行程序進行內聯輸入,比如在使用 bc 進制轉換部分討論的 bc 計算程序。在任意交互式命令中,您可以使用 here document 代替實際文件,或代替任意行的實際輸入。

嘗試使用 here document 將多行輸入發送到 bc。鍵入清單 15 中顯示的內容。

清單 15. 將內聯輸入發送到交互式程序

$ bc << EOF> ibase=16> A> EOF10$

通常使用內聯輸入來擴展變量。嘗試清單 16 中顯示的內容。

清單 16. 內聯輸入如何擴展變量

$ BASECON=16$ bc << EOF> ibase=16> $BASECON> EOF22$

Subshell 執行

可以在一個名為 subshell 的新 Shell 中執行一個或一組命令,當前 Shell 是 SubShell 的父 Shell。Subshell 繼承父親的環境。I/O 重定向可以出現在子 Shell 和父 Shell 之間,但是 Subshell 永遠不能修改父環境。當您為了執行這些命令(比如設置變量)要更改 Shell 的環境,并且不想更改腳本自身運行所在的環境時,這就是您所期望的技術。當您想要同時在后臺啟動多個長時間運行的進程時也最好使用 Subshell。一個 Shell 可以生成多個 Subshell,而 Subshell 又可以循環生成屬于它們自身的任意數量的 Subshell。圖 1 說明了這個過程。

圖 1. Subshell 如何與它的父 Shell 交互

Shell 有時自動生成自身的 Subshell,比如在管道中使用內置命令時。在 Subshell 中,Shell $ 參數擴展到父 Shell 而不是 Subshell 的進程 ID (PID)。

在 Subshell 中運行命令

要在 Subshell 中運行一組命令,可以使用括號將其括起來。您可以使用重定向將輸入發送到 Subshell 的標準輸入,或將 Subshell 的集合輸出發送到文件或管道。

嘗試在您的 home 目錄鍵入清單 17 中顯示的內容。該示例創建一個 example 目錄和一些測試文件,前提是原來不存在 example 目錄。

清單 17. 在 Subshell 中創建一組文件

$ pwd/home/user$ (mkdir example; cd example; touch A B C)$ pwd/home/user$ cd example; lsA B C$ pwd/home/user/example$

在本例中,Shell 生成一個在后臺運行的 Subshell,建立 example 目錄,然后使用 touch 在該目錄中生成三個虛擬文件。同時,Shell 返回 home 目錄的命令行。

當您有一組執行時間長的命令時,在命令行和腳本中使用 Subshell 都很方便。為了讓 Shell 保持空閑,您可以在后臺運行 Subshell,或者在后臺運行許多個 Subshell。

( group-of-long-running-commands ) &( another-group-of-long-running-commands ) &( yet-another-group-of-long-running-commands ) &

Subshell 和變量

理解變量與 Subshell 的交互方式非常重要。因為 Subshell 環境是其父親的副本,所以它繼承了父親的所有變量。但是父 Shell 從不會看到 Subshell 環境發生的任何變化,同樣,Subshell 生成以后,再也不會看到父親發生的任何變化。

作為示例,使用 vi 編輯器將清單 18 中的腳本保存到 home 目錄的 vartest 文件中,然后將其設置為可執行(請參見編寫 shell 腳本部分)。

清單 18. 演示 Subshell 中變量行為的 Shell 腳本

#!/bin/sh# Demonstrates variable behavior in a subshell environmentVAR=10echo "VAR is" $VAR(echo "In the subshell, VAR is still" $VARVAR=$(($VAR+5))echo "The new value of VAR in the subshell is" $VAR)echo "Outside of the subshell, VAR is" $VAR

現在嘗試通過鍵入腳本的名稱來執行它,如清單 19 中所示。

清單 19. vartest 腳本的輸出

$ vartestVAR is 10In the subshell, VAR is still 10The new value of VAR in the subshell is 15Outside of the subshell, VAR is 10$

連續循環

現在來看循環,它允許您執行重復任務,比如對一組文件執行一些操作或命令。Shell 有幾種構造循環的方法。

構造 for 循環

最常見的循環結構是 for 循環。首先定義一個變量作為循環的名稱,提供一組成員,可以是包括整數和文件名在內的任何單詞,然后提供每次重復執行的命令。每個命令都以分號結束 (;),整個命令組以位于單詞 do 和 done 之間。清單 20 描述了它的結構。

清單 20. Shell 中循環的結構

for loopname in membersdocommand;command;...command;done

在循環的第一次重復中,loopname 變量獲取第一個成員的值。然后 loopname 的值被清單中下一個成員的值替代,接下來它繼續重復直到遍歷所有成員。

在大多數 Shell 中,do 和 done 都可以被大括號所替代,如清單 21 中所示。

清單 21. Shell 循環的替代結構

for loopname in members{command;command;...command;}

鍵入清單 22 中的文本來運行包含三個成員的簡單循環:

清單 22. 使用循環來改變變量的值

$ for i in 1 2 3> {> VAR = $(($VAR+$i))> echo $i:$VAR> }1:12:33:6$

針對目錄中的每個文件執行命令

您可以使用循環針對給定的一組文件執行一個或一組命令。如果您提供文件的名稱作為 for 循環的成員,那么循環按您提供名稱的順序在每個文件上執行操作。您可以兩次提供同一個文件,循環將依次對該文件執行操作。在您的 example 目錄中嘗試使用清單 23 中的文本執行上述操作。

清單 23. 利用一組文件構造循環

$ cd ~/example$ lsA B C$ for file in C B B C> {> echo $file> }CBBC$

要對同一目錄下的所有文件執行操作,可以使用星號 (*) 作為循環的唯一成員,如清單 24 中所示。Shell 將星號擴展為目錄中的所有文件。然后,對于循環中您要對所有文件執行的命令,使用 loopname 變量作為合適的參數或選項。

清單 24. 針對目錄中的所有文件執行同一命令

$ lsA B C$ for file in *> {> mv $file $((0x$file))> }$

如果您正在運行本教程中的所有示例,那么您的 example 目錄中的內容應該已改變:

$ ls10 11 12$

發生的情況是循環中的 mv 命令將文件的名稱從十六進制值(通過在名稱的前面插入 0x 構成)更改為與它相等的十進制值。

構造 while 循環

您可以構造一種當滿足某些條件就一直運行的循環。使用 while 條件語句來實現這一目標,其格式如清單 25 所示。

清單 25. Shell while 循環的結構

while [ condition ]; docommand;command;...command;done

在循環中,condition 可以是使用操作符(請參見表 3)構建的語句,或者可以像一個變量名那樣簡單。只要值是非 0 的,就代表真。

表 3. 常用 Shell 操作符

操作符描述-eq 等于-ne 不等于-lt 小于-le 小于等于-gt 大于-ge 大于等于

構造 while 循環時,有一些注意事項需要牢記在心。首先,在條件與將它括起來的括號之間必須留有空白字符。其次,如果在條件中將變量用于數字比較,那么在 while 語句之前必須首先定義該變量。

鍵入清單 26 中的文本以執行一個簡短的 while 循環:

清單 26. 使用 while 循環更改變量

$ VAR=0$ while [ $VAR -lt 10 ]; do>  echo $VAR;>  VAR=$(($VAR+1));> done0123456789$

構造 until 循環

until 條件語句與 while 相似并使用相同的操作符,但是它們的行為相反。它只有當條件為假時才執行循環,并且循環持續重復直到 給定的條件為真。它的格式在清單 27 中說明。

清單 27. Shell until 循環的結構

until [ condition ] ; docommand;command;...command;done

通過鍵入清單 28 中所示的內容嘗試運行一個簡短的 until 循環:

清單 28. 使用 until 循環更改變量

$ VAR=10$ until [ $VAR -eq 0 ]; do>  echo $VAR;>  VAR=$(($VAR-1));> done10987654321$

嵌套多重循環

您可以嵌套循環和組合多種類型的循環來執行各種類型的復雜操作。由于 for 循環的成員不必是數字或以任意類型的順序排列,因此您可以使用稍后在某個內部循環中作為命令執行的命令名稱作為其成員,比如 printf、echo、stop、resume,等等。

嘗試運行清單 29 中的示例。這是一個執行算術替換的 until 循環,同時嵌套在循環詞未按數字順序排列的 for 循環內部。

清單 29. 使用嵌套循環進行算術替換

$ for i in 250 100 2136 875> {>VAR=10;>until [ $VAR -eq 0 ]; do> echo "$i / $VAR = $(($i/$VAR)) $i * $VAR = $(($i*$VAR)) $i + $VAR = $(($i+$VAR)) $i - $VAR = $(($i-$VAR))";> VAR=$(($VAR-1);>done;> }250 / 10 = 25 250 * 10 = 2500 250 + 10 = 260 250 - 10 = 240250 / 9 = 27 250 * 9 = 2250 250 + 9 = 259 250 - 9 = 241250 / 8 = 31 250 * 8 = 2000 250 + 8 = 258 250 - 8 = 242250 / 7 = 35 250 * 7 = 1750 250 + 7 = 257 250 - 7 = 243250 / 6 = 41 250 * 6 = 1500 250 + 6 = 256 250 - 6 = 244250 / 5 = 50 250 * 5 = 1250 250 + 5 = 255 250 - 5 = 245250 / 4 = 62 250 * 4 = 1000 250 + 4 = 254 250 - 4 = 246250 / 3 = 83 250 * 3 = 750 250 + 3 = 253 250 - 3 = 247250 / 2 = 125 250 * 2 = 500 250 + 2 = 252 250 - 2 = 248250 / 1 = 250 250 * 1 = 250 250 + 1 = 251 250 - 1 = 249100 / 10 = 10 100 * 10 = 1000 100 + 10 = 110 100 - 10 = 90100 / 9 = 11 100 * 9 = 900 100 + 9 = 109 100 - 9 = 91100 / 8 = 12 100 * 8 = 800 100 + 8 = 108 100 - 8 = 92100 / 7 = 14 100 * 7 = 700 100 + 7 = 107 100 - 7 = 93100 / 6 = 16 100 * 6 = 600 100 + 6 = 106 100 - 6 = 94100 / 5 = 20 100 * 5 = 500 100 + 5 = 105 100 - 5 = 95100 / 4 = 25 100 * 4 = 400 100 + 4 = 104 100 - 4 = 96100 / 3 = 33 100 * 3 = 300 100 + 3 = 103 100 - 3 = 97100 / 2 = 50 100 * 2 = 200 100 + 2 = 102 100 - 2 = 98100 / 1 = 100 100 * 1 = 100 100 + 1 = 101 100 - 1 = 992136 / 10 = 213 2136 * 10 = 21360 2136 + 10 = 2146 2136 - 10 = 21262136 / 9 = 237 2136 * 9 = 19224 2136 + 9 = 2145 2136 - 9 = 21272136 / 8 = 267 2136 * 8 = 17088 2136 + 8 = 2144 2136 - 8 = 21282136 / 7 = 305 2136 * 7 = 14952 2136 + 7 = 2143 2136 - 7 = 21292136 / 6 = 356 2136 * 6 = 12816 2136 + 6 = 2142 2136 - 6 = 21302136 / 5 = 427 2136 * 5 = 10680 2136 + 5 = 2141 2136 - 5 = 21312136 / 4 = 534 2136 * 4 = 8544 2136 + 4 = 2140 2136 - 4 = 21322136 / 3 = 712 2136 * 3 = 6408 2136 + 3 = 2139 2136 - 3 = 21332136 / 2 = 1068 2136 * 2 = 4272 2136 + 2 = 2138 2136 - 2 = 21342136 / 1 = 2136 2136 * 1 = 2136 2136 + 1 = 2137 2136 - 1 = 2135875 / 10 = 87 875 * 10 = 8750 875 + 10 = 885 875 - 10 = 865875 / 9 = 97 875 * 9 = 7875 875 + 9 = 884 875 - 9 = 866875 / 8 = 109 875 * 8 = 7000 875 + 8 = 883 875 - 8 = 867875 / 7 = 125 875 * 7 = 6125 875 + 7 = 882 875 - 7 = 868875 / 6 = 145 875 * 6 = 5250 875 + 6 = 881 875 - 6 = 869875 / 5 = 175 875 * 5 = 4375 875 + 5 = 880 875 - 5 = 870875 / 4 = 218 875 * 4 = 3500 875 + 4 = 879 875 - 4 = 871875 / 3 = 291 875 * 3 = 2625 875 + 3 = 878 875 - 3 = 872875 / 2 = 437 875 * 2 = 1750 875 + 2 = 877 875 - 2 = 873875 / 1 = 875 875 * 1 = 875 875 + 1 = 876 875 - 1 = 874$

讀取鍵盤輸入

您還可以在腳本中或從命令行本身讀取鍵盤輸入。使用 read 命令可以實現這一功能,這是一個內置函數,將任意數量的變量名作為參數。它從標準輸入讀取變量的值,讀入單行輸入并將各個輸入詞分配給各個變量。

嘗試讀取一個變量,如清單 30 中所示:

清單 30. 使用 read 讀取一個變量

$ read VAR23$ echo $VAR23$

使用 -p 選項為每次 read 提供提示。使用以引號括起來的字符串提供提示,如清單 31 中所示。發生變量擴展。

清單 31. 在變量讀取時使用提示

$ read -p "Instead of $VAR, what number would you like? " VARInstead of 23, what number would you like? 17$ echo $VAR17$

如果鍵盤輸入的詞比變量個數多,那么依次為變量分配輸入的詞,到最后一個變量時,為其分配輸入行余下的部分。(如果輸入的詞比變量個數少,那么為變量分配值直到所有的輸入都已分配,然后為所有剩余的變量分配空值。)

在循環中讀取

您可以在循環中使用 read 作為條件表達式?,F在使用清單 32 中的內容嘗試這一操作:

清單 32. 在循環中讀取一組文件名

$ while read -p "File? " file; do ls $file; doneFile? 1010File? 1212File? 4242: no such file or DirectoryFile?Carriage return10 11 12File?Control-C$

此技術通常在對循環的輸入使用管道時使用。嘗試鍵入清單 33 中的文本,該文本使用循環替代 ls 命令的輸出:

清單 33. 從管道讀取

$ ls | while read file; do ls $file; done101112$

您還可以跨多行操作變量,比如將一條消息發送到標準輸出,然后對 loopname 變量執行 Shell 運算(請參見 Shell 運算和進制轉換部分)。嘗試清單 34 中提供的示例:

清單 34. 使用管道讀取的較長循環

$ ls | while read file; do echo "The file is " `ls -i $file`;echo "If the number were in hex, the value would be $((16#$file))"doneThe file is 100267120 10If the number were in hex, the value would be 16The file is 100267121 11If the number were in hex, the value would be 17The file is 100267122 12If the number were in hex, the value would be 18$

您可以在一個管道輸入的 read 中讀取多個值,如清單 35 中所示。

清單 35. 從一個管道讀取多個變量

$ ls -i | while read inode file; doecho "File $file has inode $inode"doneFile 10 has inode 100267120File 11 has inode 100267121File 12 has inode 100267122$

實際運用

此結束部分將您在前面學到的訣竅和技術加以組合來實現在實際中有用的單命令行程序。它還包括一個簡單的 Shell 腳本——執行任意進制的轉換。

有用的單命令行程序

以下示例是執行有用功能的 Shell 單命令行程序樣本。它們全部由本教程中描述的各種結構組成。

從當前目錄中獲取一組文件名恰好為兩個字符長的文件,并使用 .ppm 擴展名為其重新命名:

for i in ??; { mv $i $i.ppm; }

使用 tar 和 Subshell 復制整個目錄樹,同時保持相同的文件權限:

( cd source ; tar pcf - * ) | ( cd target ; tar pxvf - )

讀取二進制數并以十進制輸出值:

read BINLOC;echo $((2#$BINLOC))

在 /usr/local 目錄樹中找到所有帶 .MP3 擴展名的文件(這些文件的名稱中可能包含空格字符),然后使用 bzip2 實用程序壓縮這些文件:

find /usr/local -name "*.mp3" | while read name ; do bzip2 $name; done

將給定文件中所有十進制數的值以十六進制輸出:

cat file | while read number ; do echo $((0x$number)); done

將給定文件中所有十進制數轉換為十六進制的值,并將值輸出到帶有 .hex 擴展名的新文件中:

cat file | while read number ; do echo $((0x$number)) >> file.hex; done

構造重復十次的循環,以數字(從 0 到 90 以 10 遞增)作為傳遞的參數運行 command:

i=0; while [ $i -ne 100 ]; do command $i; i=$(($i+10)); done

示例腳本:將數字轉換為其他進制

本教程中討論的一些訣竅在清單 36 中被組合在一起。它是一個示例腳本——baseconv,將數字從給定的輸入進制轉換為輸出進制。為它提供輸入進制和輸出進制的值作為參數,然后它從鍵盤輸入讀取數字,直到讀取了數字 0。

清單 36. 轉換進制的簡單腳本

#!/bin/sh# baseconv, convert numbers from one base to another.#NUMBER=1while [ $NUMBER ]; doread -p "Input base: " INread -p "Output base: " OUTread -p "Number: " NUMBERbc -ql <<- EOFobase=$OUTibase=$IN$NUMBEREOFdone

當您把它保存到可執行文件后(請參見創建 Shell 腳本部分),嘗試運行該文件,如清單 37 中所示:

清單 37. baseconv 腳本的輸出

$ ./baseconvInput base: 10Output base: 16Number: 3321Input base: 2Output base: 1100Number: 1015Input base: 16Output base: ANumber: ACA2762Input base: 10Output base: 10Number:Carriage return$

結束語

總結

噢!本教程確實涵蓋了許多內容,帶您快速瀏覽了基本的 Shell 編程概念。在學習本教程的過程中,您了解了有關 Shell 編程的許多核心概念:連續循環、內聯輸入、讀取鍵盤輸入、進制轉換及 Subshell 執行。您還了解到 Shell 代碼片段如何能夠作為單命令行程序直接從 Shell 提示符上運行,以及如何將它們放在同一個文件中作為可執行腳本。您可以從中學習一些最重要的腳本編程概念。您如果能綜合運用在本教程以及本系列教程的前面部分學到的知識,那么您已成功地邁上 UNIX 專家之路。

標簽: Unix系統
国产综合久久一区二区三区