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

轉:http://www.ibm.com/developerworks/cn/education/aix/au-unixtips4/section2.html

Shell 命令執行

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

引用

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

$ echo "Hello, world"
Hello, world

 

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

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

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

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

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

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

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

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


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

                    
$ myvar = "Hello, world"
$ echo $myvar
Hello, 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 ignored
Hello, 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 script
message = "Hello, world!"
echo "The message is '"$message"'"

 

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

$ chmod u+x myscript
                

 

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

$ chmod a+x myscript
                

 

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

$ ./myscript
The 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
$ myscript
myscript: command not found
$ PATH = $PATH":."
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:.
$ myscript
The message is 'Hello, world!'
$

 

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

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

$ echo $-
himBH
$

 

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


表 1. AIX Bourne Shell 的常用選項

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

 

Shell 運算和進制轉換

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

$(( expression ))

 

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


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

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

 

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


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

                    
$ myvar = 10
$ echo $myvar
10
$ echo $(($myvar-2))
8
$ myvar = $(($myvar+5))
$ echo $myvar
15
$ result = $(($myvar-10))
$ echo $result
5
$

 

表 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 -ql
                    10
10
obase=16
                    10
A
ibase=2
                    10
2

                        Control-D
                    
$

 

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


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

                    
$ echo 'obase=16; 47' | bc
2F
$ echo 'obase=10; ibase=16; A03' | bc
2563
$

 

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


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

                    
$ echo 'ibase=16; obase=10; A' | bc
A
$ echo 'ibase=16; obase=A; A' | bc
10
$

 

 

內聯輸入

儘管 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
> END
 END of input text
END 
This is still not the END
ENDING SOON
THE END
$

 

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

腳本中的內聯輸入

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


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

                    
#!/bin/sh
cat << EOF
baseconv is a program to convert a number from one base to another.

Usage: baseconv [options]

Options:

-i	BASE input base
-o	BASE output base
-h	display this message

For more information, consult the baseconv man page.
EOF

 

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


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

                    
$ baseconv
baseconv is a program to convert a number from one base to another.

Usage: baseconv [options]

Options:

-i	BASE input base
-o	BASE output base
-h	display this message

For more information, consult the baseconv man page.
$

 

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


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

                    
#!/bin/sh

	cat <<- EOF
		baseconv is a program to convert a number from one base to another.

		Usage: baseconv [options]

		Options:

		-i	BASE input base
		-o	BASE output base
		-h	display this message

		For more information, consult the baseconv man page.
		EOF

 

 

在命令行使用內聯輸入

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

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


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

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

 

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


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

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

 

 

Subshell 執行

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


圖 1. Subshell 如何與它的父 Shell 交互
圖示 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; ls
A 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 environment
 
VAR=10

echo "VAR is" $VAR

(

echo "In the subshell, VAR is still" $VAR

VAR=$(($VAR+5))

echo "The new value of VAR in the subshell is" $VAR

)

echo "Outside of the subshell, VAR is" $VAR

 

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


清單 19. vartest 腳本的輸出

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

 

 

連續循環

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

構造 for 循環

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


清單 20. Shell 中循環的結構

                    
for loopname in members

do

	command;
	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:1
2:3
3:6
$

 

 

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

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


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

                    
$ cd ~/example
$ ls
A B C
$ for file in C B B C
> {
> echo $file
> }
C
B
B
C
$

 

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


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

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

 

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

$ ls
10 11 12
$

 

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

構造 while 循環

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


清單 25. Shell while 循環的結構

                    
while [ condition ]; do

	command;
	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));
> done
0
1
2
3
4
5
6
7
8
9
$

 

構造 until 循環

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


清單 27. Shell until 循環的結構

                    
until [ condition ] ; do

	command;
	command;
	...
	command;
done

 

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


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

                    
$ VAR=10
$ until [ $VAR -eq 0 ]; do
>   echo $VAR;
>   VAR=$(($VAR-1));
> done
10
9
8
7
6
5
4
3
2
1
$

 

嵌套多重循環

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

嘗試運行清單 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 = 240
250 / 9 = 27  250 * 9 = 2250  250 + 9 = 259  250 - 9 = 241
250 / 8 = 31  250 * 8 = 2000  250 + 8 = 258  250 - 8 = 242
250 / 7 = 35  250 * 7 = 1750  250 + 7 = 257  250 - 7 = 243
250 / 6 = 41  250 * 6 = 1500  250 + 6 = 256  250 - 6 = 244
250 / 5 = 50  250 * 5 = 1250  250 + 5 = 255  250 - 5 = 245
250 / 4 = 62  250 * 4 = 1000  250 + 4 = 254  250 - 4 = 246
250 / 3 = 83  250 * 3 = 750  250 + 3 = 253  250 - 3 = 247
250 / 2 = 125  250 * 2 = 500  250 + 2 = 252  250 - 2 = 248
250 / 1 = 250  250 * 1 = 250  250 + 1 = 251  250 - 1 = 249
100 / 10 = 10  100 * 10 = 1000  100 + 10 = 110  100 - 10 = 90
100 / 9 = 11  100 * 9 = 900  100 + 9 = 109  100 - 9 = 91
100 / 8 = 12  100 * 8 = 800  100 + 8 = 108  100 - 8 = 92
100 / 7 = 14  100 * 7 = 700  100 + 7 = 107  100 - 7 = 93
100 / 6 = 16  100 * 6 = 600  100 + 6 = 106  100 - 6 = 94
100 / 5 = 20  100 * 5 = 500  100 + 5 = 105  100 - 5 = 95
100 / 4 = 25  100 * 4 = 400  100 + 4 = 104  100 - 4 = 96
100 / 3 = 33  100 * 3 = 300  100 + 3 = 103  100 - 3 = 97
100 / 2 = 50  100 * 2 = 200  100 + 2 = 102  100 - 2 = 98
100 / 1 = 100  100 * 1 = 100  100 + 1 = 101  100 - 1 = 99
2136 / 10 = 213  2136 * 10 = 21360  2136 + 10 = 2146  2136 - 10 = 2126
2136 / 9 = 237  2136 * 9 = 19224  2136 + 9 = 2145  2136 - 9 = 2127
2136 / 8 = 267  2136 * 8 = 17088  2136 + 8 = 2144  2136 - 8 = 2128
2136 / 7 = 305  2136 * 7 = 14952  2136 + 7 = 2143  2136 - 7 = 2129
2136 / 6 = 356  2136 * 6 = 12816  2136 + 6 = 2142  2136 - 6 = 2130
2136 / 5 = 427  2136 * 5 = 10680  2136 + 5 = 2141  2136 - 5 = 2131
2136 / 4 = 534  2136 * 4 = 8544  2136 + 4 = 2140  2136 - 4 = 2132
2136 / 3 = 712  2136 * 3 = 6408  2136 + 3 = 2139  2136 - 3 = 2133
2136 / 2 = 1068  2136 * 2 = 4272  2136 + 2 = 2138  2136 - 2 = 2134
2136 / 1 = 2136  2136 * 1 = 2136  2136 + 1 = 2137  2136 - 1 = 2135
875 / 10 = 87  875 * 10 = 8750  875 + 10 = 885  875 - 10 = 865
875 / 9 = 97  875 * 9 = 7875  875 + 9 = 884  875 - 9 = 866
875 / 8 = 109  875 * 8 = 7000  875 + 8 = 883  875 - 8 = 867
875 / 7 = 125  875 * 7 = 6125  875 + 7 = 882  875 - 7 = 868
875 / 6 = 145  875 * 6 = 5250  875 + 6 = 881  875 - 6 = 869
875 / 5 = 175  875 * 5 = 4375  875 + 5 = 880  875 - 5 = 870
875 / 4 = 218  875 * 4 = 3500  875 + 4 = 879  875 - 4 = 871
875 / 3 = 291  875 * 3 = 2625  875 + 3 = 878  875 - 3 = 872
875 / 2 = 437  875 * 2 = 1750  875 + 2 = 877  875 - 2 = 873
875 / 1 = 875  875 * 1 = 875  875 + 1 = 876  875 - 1 = 874
$

 

 

讀取鍵盤輸入

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

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


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

                    
$ read VAR
                    23
$ echo $VAR
23
$

 

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


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

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

 

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

在循環中讀取

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


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

                    
$ while read -p "File? " file; do ls $file; done
File? 10
10
File? 12
12
File? 42
42: no such file or directory
File? 
                        Carriage return
                    
10 11 12
File? 
                        Control-C
                    
$

 

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


清單 33. 從管道讀取

                    
$ ls | while read file; do ls $file; done
10
11
12
$

 

您還可以跨多行操作變量,比如將一條消息發送到標準輸出,然後對 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))"; done
The file is  100267120 10
If the number were in hex, the value would be 16
The file is  100267121 11
If the number were in hex, the value would be 17
The file is  100267122 12
If the number were in hex, the value would be 18
$

 

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


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

                    
$ ls -i | while read inode file; do \
                    echo "File $file has inode $inode"; done
File 10 has inode 100267120
File 11 has inode 100267121
File 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=1
while [ $NUMBER ]; do
    read -p "Input base: " IN
    read -p "Output base: " OUT
    read -p "Number: " NUMBER
    bc -ql <<- EOF
		obase=$OUT
		ibase=$IN
		$NUMBER
		EOF
done

 

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


清單 37. baseconv 腳本的輸出

                    
$ ./baseconv
Input base: 10
Output base: 16
Number: 33
21
Input base: 2
Output base: 1100
Number: 101
5
Input base: 16
Output base: A
Number: ACA
2762
Input base: 10
Output base: 10
Number: 
                        Carriage return
                    
$

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章