鍍金池/ 教程/ Linux/ 從 shell 眼中看世界
網(wǎng)絡(luò)系統(tǒng)
打印
重定向
使用命令
位置參數(shù)
權(quán)限
文本處理
疑難排解
layout: book-zh title: 自定制 shell 提示符
查找文件
layout: book-zh title: vi 簡介
shell 環(huán)境
什么是 shell
編譯程序
鍵盤高級操作技巧
流程控制:case 分支
流程控制:if 分支結(jié)構(gòu)
layout: book-zh title: 軟件包管理
進(jìn)程
存儲媒介
格式化輸出
編寫第一個 Shell 腳本
啟動一個項目
流程控制:while/until 循環(huán)
文件系統(tǒng)中跳轉(zhuǎn)
字符串和數(shù)字
讀取鍵盤輸入
歸檔和備份
探究操作系統(tǒng)
流程控制:for 循環(huán)
自頂向下設(shè)計
數(shù)組
操作文件和目錄
奇珍異寶
從 shell 眼中看世界
正則表達(dá)式

從 shell 眼中看世界

在這一章我們將看一下,當(dāng)你按下 enter 鍵后,發(fā)生在命令行中的一些“魔法”。雖然我們會 仔細(xì)查看幾個復(fù)雜有趣的 shell 特點(diǎn),但我們只使用一個新命令來處理這些特性。

  • echo - 顯示一行文本

(字符)展開

每一次你輸入一個命令,然后按下 enter 鍵,在 bash 執(zhí)行你的命令之前,bash 會對輸入 的字符完成幾個步驟處理。我們已經(jīng)知道兩三個案例,怎樣一個簡單的字符序列,例如"*", 對 shell 來說,有很多的涵義。使這個發(fā)生的過程叫做(字符)展開。通過展開, 你輸入的字符,在 shell 對它起作用之前,會展開成為別的字符。為了說明我們所要 表達(dá)的意思,讓我們看一看 echo 命令。echo 是一個 shell 內(nèi)部命令,來完成非常簡單的任務(wù)。 它在標(biāo)準(zhǔn)輸出中打印出它的文本參數(shù)。

[me@linuxbox ~]$ echo this is a test
this is a test

這個命令的作用相當(dāng)簡單明了。傳遞到 echo 命令的任一個參數(shù)都會在(屏幕上)顯示出來。 讓我們試試另一個例子:

[me@linuxbox ~]$ echo *
Desktop Documents ls-output.txt Music Pictures Public Templates Videos

那么剛才發(fā)生了什么事情呢? 為什么 echo 不打印"*"呢?隨著你回想起我們所學(xué)過的 關(guān)于通配符的內(nèi)容,這個"*"字符意味著匹配文件名中的任意字符,但是在原先的討論 中我們卻不知道 shell 是怎樣實(shí)現(xiàn)這個功能的。最簡單的答案就是 shell 把"*"展開成了 另外的東西(在這種情況下,就是在當(dāng)前工作目錄下的文件名字),在 echo 命令被執(zhí)行 前。當(dāng)回車鍵被按下時,shell 在命令被執(zhí)行前在命令行上自動展開任何符合條件的字符, 所以 echo 命令從不會發(fā)現(xiàn)"*",只把它展開成結(jié)果。知道了這個以后,我們能看到 echo 執(zhí)行 的結(jié)果和我們想象的一樣。

路徑名展開

這種通配符工作機(jī)制叫做路徑名展開。如果我們試一下在之前的章節(jié)中使用的技巧, 我們會看到它們真是要展開的字符。給出一個家目錄,它看起來像這樣:

[me@linuxbox ~]$ ls
Desktop   ls-output.txt   Pictures   Templates
....

我們能夠執(zhí)行以下參數(shù)展開模式:

[me@linuxbox ~]$ echo D*
Desktop  Documents

和:

[me@linuxbox ~]$ echo *s
Documents Pictures Templates Videos

甚至是:

[me@linuxbox ~]$ echo [[:upper:]]*
Desktop Documents Music Pictures Public Templates Videos

查看家目錄之外的目錄:

[me@linuxbox ~]$ echo /usr/*/share
/usr/kerberos/share  /usr/local/share

隱藏文件路徑名展開

正如我們知道的,以圓點(diǎn)字符開頭的文件名是隱藏文件。路徑名展開也尊重這種 行為。像這樣的展開:

echo *

不會顯示隱藏文件

要是展開模式以一個圓點(diǎn)開頭,我們就能夠在展開模式中包含隱藏文件, 而且隱藏文件可能會出現(xiàn)在第一位置,就像這樣:

echo .*

它幾乎是起作用了。然而,如果我們仔細(xì)檢查一下輸出結(jié)果,我們會看到名字"." 和".."也出現(xiàn)在結(jié)果中。因?yàn)檫@些名字是指當(dāng)前工作目錄和它的父目錄,使用這種 模式可能會產(chǎn)生不正確的結(jié)果。我們能看到這樣的結(jié)果,如果我們試一下這個命令:

ls -d .* | less

為了在這種情況下正確地完成路徑名展開,我們應(yīng)該雇傭一個更精確些的模式。 這個模式會正確地工作:

ls -d .[!.]?*

這種模式展開成為文件名,每個文件名以圓點(diǎn)開頭,第二個字符不包含圓點(diǎn),再包含至少一個字符, 并且這個字符之后緊接著任意多個字符。這將列出大多數(shù)的隱藏文件 (但仍將不能包含以多個圓點(diǎn)開頭的文件名)這個帶有 -A 選項(“幾乎所有”)的 ls 命令能夠提供一份正確的隱藏文件清單:

ls -A

波浪線展開

可能你從我們對 cd 命令的介紹中回想起來,波浪線字符("~")有特殊的意思。當(dāng)它用在 一個單詞的開頭時,它會展開成指定用戶的家目錄名,如果沒有指定用戶名,則是當(dāng)前用戶的家目錄:

[me@linuxbox ~]$ echo ~
/home/me

如果有用戶"foo"這個帳號,然后:

[me@linuxbox ~]$ echo ~foo
/home/foo

算術(shù)表達(dá)式展開

shell 允許算術(shù)表達(dá)式通過展開來執(zhí)行。這允許我們把 shell 提示當(dāng)作計算器來使用:

[me@linuxbox ~]$ echo $((2 + 2))
4

算術(shù)表達(dá)式展開使用這種格式:

$((expression))

(以上括號中的)表達(dá)式是指算術(shù)表達(dá)式,它由數(shù)值和算術(shù)操作符組成。

算術(shù)表達(dá)式只支持整數(shù)(全部是數(shù)字,不帶小數(shù)點(diǎn)),但是能執(zhí)行很多不同的操作。這里是 一些它支持的操作符:

表 8-1: 算術(shù)操作符
操作符 說明
+
-
*
/ 除(但是記住,因?yàn)檎归_只是支持整數(shù)除法,所以結(jié)果是整數(shù)。)
% 取余,只是簡單的意味著,“余數(shù)”
** 取冪

在算術(shù)表達(dá)式中空格并不重要,并且表達(dá)式可以嵌套。例如,5的平方乘以3:

[me@linuxbox ~]$ echo $(($((5**2)) * 3))
75

一對括號可以用來把多個子表達(dá)式括起來。通過這個技術(shù),我們可以重寫上面的例子, 同時用一個展開代替兩個,來得到一樣的結(jié)果:

[me@linuxbox ~]$ echo $(((5**2) * 3))
75

這是一個使用除法和取余操作符的例子。注意整數(shù)除法的結(jié)果:

[me@linuxbox ~]$ echo Five divided by two equals $((5/2))
Five divided by two equals 2
[me@linuxbox ~]$ echo with $((5%2)) left over.
with 1 left over.

在35章會更深入的討論算術(shù)表達(dá)式的內(nèi)容。

花括號展開

可能最奇怪的展開是花括號展開。通過它,你可以從一個包含花括號的模式中 創(chuàng)建多個文本字符串。這是一個例子:

[me@linuxbox ~]$ echo Front-{A,B,C}-Back
Front-A-Back Front-B-Back Front-C-Back

花括號展開模式可能包含一個開頭部分叫做報頭,一個結(jié)尾部分叫做附言?;ɡㄌ柋磉_(dá)式本身可 能包含一個由逗號分開的字符串列表,或者一系列整數(shù),或者單個的字符串。這種模式不能 嵌入空白字符。這個例題使用了一系列整數(shù):

[me@linuxbox ~]$ echo Number_{1..5}
Number_1  Number_2  Number_3  Number_4  Number_5

一系列以倒序排列的字母:

[me@linuxbox ~]$ echo {Z..A}
Z Y X W V U T S R Q P O N M L K J I H G F E D C B A

花括號展開可以嵌套:

[me@linuxbox ~]$ echo a{A{1,2},B{3,4}}b
aA1b aA2b aB3b aB4b

那么這對什么有好處呢?最普遍的應(yīng)用是,創(chuàng)建一系列的文件或目錄列表。例如, 如果我們是攝影師,有大量的相片。我們想把這些相片按年月先后組織起來。首先, 我們要創(chuàng)建一系列以數(shù)值"年-月"形式命名的目錄。通過這種方式,目錄名按照 年代順序排列。我們可以鍵入整個目錄列表,但是工作量太大了,并且易于出錯。 反而,我們可以這樣做:

[me@linuxbox ~]$ mkdir Pics
[me@linuxbox ~]$ cd Pics
[me@linuxbox Pics]$ mkdir {2007..2009}-0{1..9} {2007..2009}-{10..12}
[me@linuxbox Pics]$ ls
2007-01 2007-07 2008-01 2008-07 2009-01 2009-07
2007-02 2007-08 2008-02 2008-08 2009-02 2009-08
2007-03 2007-09 2008-03 2008-09 2009-03 2009-09
2007-04 2007-10 2008-04 2008-10 2009-04 2009-10
2007-05 2007-11 2008-05 2008-11 2009-05 2009-11
2007-06 2007-12 2008-06 2008-12 2009-06 2009-12

棒極了!

參數(shù)展開

在這一章我們將會簡單地介紹參數(shù)展開,只是皮毛而已。后續(xù)章節(jié)我們會廣泛地 討論參數(shù)展開。這個特性在 shell 腳本中比直接在命令行中更有用。它的許多性能 和系統(tǒng)存儲小塊數(shù)據(jù),并給每塊數(shù)據(jù)命名的能力有關(guān)系。許多像這樣的小塊數(shù)據(jù), 更適當(dāng)些應(yīng)叫做變量,可以方便地檢查它們。例如,叫做"USER"的變量包含你的 用戶名。喚醒參數(shù)展開,揭示 USER 中的內(nèi)容,可以這樣做:

[me@linuxbox ~]$ echo $USER
me

查看有效的變量列表,試試這個:

[me@linuxbox ~]$ printenv | less

你可能注意到其它展開類型,如果你誤輸入一個模式,展開就不會發(fā)生。這時 echo 命令只簡單地顯示誤鍵入的模式。通過參數(shù)展開,如果你拼寫錯了一個變量名, 展開仍然會進(jìn)行,只是展成一個空字符串:

[me@linuxbox ~]$ echo $SUER

[me@linuxbox ~]$

命令替換

命令替換允許我們把一個命令的輸出作為一個展開模式來使用:

[me@linuxbox ~]$ echo $(ls)
Desktop Documents ls-output.txt Music Pictures Public Templates
Videos

我最喜歡用的一行命令是像這樣的:

[me@linuxbox ~]$ ls -l $(which cp)
-rwxr-xr-x 1 root root 71516 2007-12-05 08:58 /bin/cp

這里我們把 which cp 的執(zhí)行結(jié)果作為一個參數(shù)傳遞給 ls 命令,因此要想得到 cp 程序的 輸出列表,不必知道它完整的路徑名。我們不只限制于簡單命令。也可以使用整個管道線 (只展示部分輸出):

[me@linuxbox ~]$ file $(ls /usr/bin/* | grep zip)
/usr/bin/bunzip2:     symbolic link to `bzip2'
....

在這個例子中,管道線的輸出結(jié)果成為 file 命令的參數(shù)列表。

在舊版 shell 程序中,有另一種語法也支持命令替換,可與剛提到的語法輪換使用。 bash 也支持這種語法。它使用倒引號來代替美元符號和括號:

[me@linuxbox ~]$ ls -l `which cp`
-rwxr-xr-x 1 root root 71516 2007-12-05 08:58 /bin/cp

引用

我們已經(jīng)知道 shell 有許多方式可以完成展開,現(xiàn)在是時候?qū)W習(xí)怎樣來控制展開了。 以下面例子來說明:

[me@linuxbox ~]$ echo this is a    test
this is a test

或者:

[me@linuxbox ~]$ echo The total is $100.00
The total is 00.00

在第一個例子中,shell 從 echo 命令的參數(shù)列表中,刪除多余的空格。在第二個例子中, 參數(shù)展開把 $1 的值替換為一個空字符串,因?yàn)?1 是沒有定義的變量。shell 提供了一種 叫做引用的機(jī)制,來有選擇地禁止不需要的展開。

雙引號

我們將要看一下引用的第一種類型,雙引號。如果你把文本放在雙引號中, shell 使用的特殊字符,除了 $,\ (反斜杠),和 `(倒引號)之外, 則失去它們的特殊含義,被當(dāng)作普通字符來看待。這意味著單詞分割,路徑名展開, 波浪線展開,和花括號展開都被禁止,然而參數(shù)展開,算術(shù)展開,和命令替換 仍然執(zhí)行。使用雙引號,我們可以處理包含空格的文件名。比方說我們是不幸的 名為 two words.txt 文件的受害者。如果我們試圖在命令行中使用這個 文件,單詞分割機(jī)制會導(dǎo)致這個文件名被看作兩個獨(dú)自的參數(shù),而不是所期望 的單個參數(shù):

[me@linuxbox ~]$ ls -l two words.txt
ls: cannot access two: No such file or directory
ls: cannot access words.txt: No such file or directory

使用雙引號,我們可以阻止單詞分割,得到期望的結(jié)果;進(jìn)一步,我們甚至可以修復(fù) 破損的文件名。

[me@linuxbox ~]$ ls -l "two words.txt"
-rw-rw-r-- 1 me   me   18 2008-02-20 13:03 two words.txt
[me@linuxbox ~]$ mv "two words.txt" two_words.txt

你瞧!現(xiàn)在我們不必一直輸入那些討厭的雙引號了。

記住,在雙引號中,參數(shù)展開,算術(shù)表達(dá)式展開,和命令替換仍然有效:

[me@linuxbox ~]$ echo "$USER $((2+2)) $(cal)"
me 4    February 2008
Su Mo Tu We Th Fr Sa
....

我們應(yīng)該花費(fèi)一點(diǎn)時間來看一下雙引號在命令替換中的效果。首先仔細(xì)研究一下單詞分割 是怎樣工作的。在之前的范例中,我們已經(jīng)看到單詞分割機(jī)制是怎樣來刪除文本中額外空格的:

[me@linuxbox ~]$ echo this is a   test
this is a test

在默認(rèn)情況下,單詞分割機(jī)制會在單詞中尋找空格,制表符,和換行符,并把它們看作 單詞之間的界定符。它們只作為分隔符使用。因?yàn)樗鼈儼褑卧~分為不同的參數(shù),在范例中, 命令行包含一個帶有四個不同參數(shù)的命令。如果我們加上雙引號:

[me@linuxbox ~]$ echo "this is a    test"
this is a    test

單詞分割被禁止,內(nèi)嵌的空格也不會被當(dāng)作界定符,它們成為參數(shù)的一部分。 一旦加上雙引號,我們的命令行就包含一個帶有一個參數(shù)的命令。

事實(shí)上,單詞分割機(jī)制把換行符看作界定符,對命令替換產(chǎn)生了一個,雖然微妙,但有趣的影響。 考慮下面的例子:

[me@linuxbox ~]$ echo $(cal)
February 2008 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
[me@linuxbox ~]$ echo "$(cal)"
February 2008
....

在第一個實(shí)例中,沒有引用的命令替換導(dǎo)致命令行包含38個參數(shù)。在第二個例子中, 命令行只有一個參數(shù),參數(shù)中包括嵌入的空格和換行符。

單引號

如果需要禁止所有的展開,我們使用單引號。以下例子是無引用,雙引號,和單引號的比較結(jié)果:

[me@linuxbox ~]$ echo text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER
text /home/me/ls-output.txt a b foo 4 me
[me@linuxbox ~]$ echo "text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER"
text ~/*.txt   {a,b} foo 4 me
[me@linuxbox ~]$ echo 'text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER'
text ~/*.txt  {a,b} $(echo foo) $((2+2)) $USER

正如我們所看到的,隨著引用程度加強(qiáng),越來越多的展開被禁止。

轉(zhuǎn)義字符

有時候我們只想引用單個字符。我們可以在字符之前加上一個反斜杠,在這個上下文中叫做轉(zhuǎn)義字符。 經(jīng)常在雙引號中使用轉(zhuǎn)義字符,來有選擇地阻止展開。

[me@linuxbox ~]$ echo "The balance for user $USER is: \$5.00"
The balance for user me is: $5.00

使用轉(zhuǎn)義字符來消除文件名中一個字符的特殊含義,是很普遍的。例如,在文件名中可能使用 一些對于 shell 來說,有特殊含義的字符。這些字符包括"$", "!", " "等字符。在文件名 中包含特殊字符,你可以這樣做:

[me@linuxbox ~]$ mv bad\&filename good_filename

為了允許反斜杠字符出現(xiàn),輸入"\"來轉(zhuǎn)義。注意在單引號中,反斜杠失去它的特殊含義,它 被看作普通字符。

反斜杠轉(zhuǎn)義字符序列

反斜杠除了作為轉(zhuǎn)義字符外,反斜杠也是一種表示法的一部分,這種表示法代表某種 特殊字符,叫做控制碼。ASCII 編碼表中前32個字符被用來把命令轉(zhuǎn)輸?shù)较耠妶髾C(jī) 一樣的設(shè)備。一些編碼是眾所周知的(制表符,退格符,換行符,和回車符),其它 一些編碼就不熟悉了(空值,傳輸結(jié)束碼,和確認(rèn))。

|轉(zhuǎn)義序列|含義 |\a|響鈴("警告"-導(dǎo)致計算機(jī)嘟嘟響) |\b|退格符 |\n|新的一行。在類 Unix 系統(tǒng)中,產(chǎn)生換行。 |\r|回車符 |\t|制表符

上表列出了一些常見的反斜杠轉(zhuǎn)義字符。反斜杠表示法背后的思想來源于 C 編程語言, 許多其它語言也采用了這種表示方法,包括 shell。

echo 命令帶上 '-e' 選項,能夠解釋轉(zhuǎn)義序列。你可以把轉(zhuǎn)義序列放在 $\' \' 里面。 以下例子,使用 sleep 命令,一個簡單的程序,它會等待指定的秒數(shù),然后退出。 我們可以創(chuàng)建一個簡單的倒數(shù)計數(shù)器:

sleep 10; echo -e \"Time\'s up\a\"

我們也可以這樣做:

sleep 10; echo \"Time\'s up\" $\'\a\'

總結(jié)歸納

隨著我們繼續(xù)學(xué)習(xí) shell,你會發(fā)現(xiàn)使用展開和引用的頻率逐漸多起來,所以能夠很好的 理解他們的工作方式很有意義。事實(shí)上,可以這樣說,他們是學(xué)習(xí) shell 的最重要的主題。 如果沒有準(zhǔn)確地理解展開模式,shell 總是神秘和混亂的源泉,并且 shell 潛在的能力也 浪費(fèi)掉了。

拓展閱讀

上一篇:什么是 shell下一篇:編譯程序