還是回到我們的command line來(lái)吧...
經(jīng)過(guò)前面兩章的學(xué)習(xí),應(yīng)該很清楚當(dāng)你在shell prompt后面敲打鍵盤, 直到按下Enter鍵的時(shí)候,你輸入的文字就是command line了, 然后shell才會(huì)以進(jìn)程的方式執(zhí)行你所交給它的命令。 但是,你又可知道:你在command line中輸入的每一個(gè)文字, 對(duì)shell來(lái)說(shuō),是有類別之分的呢?
簡(jiǎn)單而言,(我不敢說(shuō)精確的定義,注 1),command line的每一個(gè)charactor, 分為如下兩種:
shell來(lái)說(shuō)沒(méi)特殊功能;shell來(lái)說(shuō),具有特定功能的特殊保留元字符。Note
對(duì)于
bash shell在處理comamnd line的順序說(shuō)明, 請(qǐng)參考 O'Reilly 出版社的 Learning the Bash Shell,2nd Edition, 第 177-180 頁(yè)的說(shuō)明,尤其是 178 頁(yè)的流程圖:Figure 7-1 ...
literal沒(méi)什么好談的, 像 abcd、123456 這些 "文字" 都是 literal...(so easy? ^_^) 但 meta 卻常使我們困惑...(confused?) 事實(shí)上,前兩章,我們?cè)?code>command line中已碰到兩個(gè) 似乎每次都會(huì)碰到的 meta:
IFS:有space或者tab或者Enter三者之一組成 (我們常用 space)CR: 由Enter產(chǎn)生;IFS是用來(lái)拆解command line中每一個(gè)詞 (word) 用的, 因?yàn)?code>shell command line是按詞來(lái)處理的。 而CR則是用來(lái)結(jié)束command line用的,這也是為何我們敲Enter鍵, 命令就會(huì)跑的原因。
除了常用的IFS與CR, 常用的 meta 還有:
| meta 字符 | meta 字符作用 |
|---|---|
| = | 設(shè)定變量 |
| $ | 作變量或運(yùn)算替換 (請(qǐng)不要與shell prompt混淆) |
| > | 輸出重定向 (重定向 stdout) |
| < | 輸入重定向 (重定向 stdin) |
| | | 命令管道 |
| & | 重定向 file descriptor 或?qū)⒚钪劣诤笈_(tái) (bg) 運(yùn)行 |
| () | 將其內(nèi)部的命令置于 nested subshell 執(zhí)行,或用于運(yùn)算或變量替換 |
| {} | 將期內(nèi)的命令置于 non-named function 中執(zhí)行,或用在變量替換的界定范圍 |
| ; | 在前一個(gè)命令執(zhí)行結(jié)束時(shí),而忽略其返回值,繼續(xù)執(zhí)行下一個(gè)命令 |
| && | 在前一個(gè)命令執(zhí)行結(jié)束時(shí),若返回值為 true,繼續(xù)執(zhí)行下一個(gè)命令 |
| || | 在前一個(gè)命令執(zhí)行結(jié)束時(shí),若返回值為 false,繼續(xù)執(zhí)行下一個(gè)命令 |
| ! | 執(zhí)行 histroy 列表中的命令 |
| ... | ... |
假如我們需要在command line中將這些保留元字符的功能關(guān)閉的話, 就需要 quoting 處理了。
在bash中,常用的 quoting 有以下三種方法:
Note:
在 soft quote 中被豁免的具體 meta 清單,我不完全知道, 有待大家補(bǔ)充,或通過(guò)實(shí)踐來(lái)發(fā)現(xiàn)并理解。
下面的例子將有助于我們對(duì) quoting 的了解:
$ A=B C #空白符未被關(guān)閉,作為IFS處理
$ C:command not found.
$ echo $A
$ A="B C" #空白符已被關(guān)掉,僅作為空白符
$ echo $A
B C
在第一個(gè)給 A 變量賦值時(shí),由于空白符沒(méi)有被關(guān)閉, command line 將被解釋為: A=B 然后碰到<IFS>,接著執(zhí)行C命令 在第二次給 A 變量賦值時(shí),由于空白符被置于 soft quote 中, 因此被關(guān)閉,不在作為IFS; A=B<space>C 事實(shí)上,空白符無(wú)論在 soft quote 還是在 hard quote 中, 均被關(guān)閉。Enter 鍵字符亦然:
$ A=``B
> C
> '
$ echo "$A"
B
C
在上例中,由于enter被置于 hard quote 當(dāng)中,因此不再作為CR字符來(lái)處理。 這里的enter單純只是一個(gè)斷行符號(hào) (new-line) 而已, 由于command line并沒(méi)得到CR字符, 因此進(jìn)入第二個(gè)shell prompt(PS2,以 > 符號(hào)表示), command line并不會(huì)結(jié)束,直到第三行, 我們輸入的enter并不在 hard quote 里面, 因此沒(méi)有被關(guān)閉, 此時(shí),command line碰到CR字符,于是結(jié)束,交給 shell 來(lái)處理。
上例的Enter要是被置于 soft quote 中的話,CR字符也會(huì)同樣被關(guān)閉:
$ A="B
> C
> "
$ echo $A
B C
然而,由于 echo $A時(shí)的變量沒(méi)有置于 soft quote 中, 因此,當(dāng)變量替換完成后,并作命令行重組時(shí),enter被解釋為IFS, 而不是 new-line 字符。
同樣的,用 escape 亦可關(guān)閉 CR 字符:
$ A=B\
> C\
>
$ echo $A
BC
上例中的,第一個(gè)enter跟第二個(gè)enter均被 escape 字符關(guān)閉了, 因此也不作為CR來(lái)處理,但第三個(gè)enter由于沒(méi)有被 escape, 因此,作為CR結(jié)束command line。 但由于enter鍵本身在 shell meta 中特殊性,在 \ escape 字符后面 僅僅取消其CR功能, 而不保留其 IFS 功能。
你或許發(fā)現(xiàn)光是一個(gè)enter鍵所產(chǎn)生的字符,就有可能是如下這些可能:
至于,什么時(shí)候解釋為什么字符,這個(gè)我就沒(méi)法去挖掘了, 或者留給讀者君自行慢慢摸索了...^-^
至于 soft quote 跟 hard quote 的不同,主要是對(duì)于某些 meta 的關(guān)閉與否,以 $ 來(lái)做說(shuō)明:
$ A=B\ C
$ echo "$A"
B C
$ echo '$A'
$A
在第一個(gè)echo命令行中,$ 被置于 soft quote 中,將不被關(guān)閉, 因此繼續(xù)處理變量替換, 因此,echo將 A 的變量值輸出到屏幕,也就是 "B C" 的結(jié)果。
在第二個(gè)echo命令行中,$ 被置于 hard quote 中,則被關(guān)閉, 因此,$ 只是一個(gè) $ 符號(hào),并不會(huì)用來(lái)做變量替換處理, 因此結(jié)果是 $ 符號(hào)后面接一個(gè) A 字母:$A。
練習(xí)與思考: 如下結(jié)果為何不同?
tips: 單引號(hào)和雙引號(hào),在 quoting 中均被關(guān)閉了。
$ A=B\ C
$ echo '"$A"' #最外面的是單引號(hào)
"$A"
$ echo "'$A'" #最外面的是雙引號(hào)
'B C'
在 CU 的 shell 版里,我發(fā)現(xiàn)很多初學(xué)者的問(wèn)題, 都與 quoting 的理解有關(guān)。 比方說(shuō),若我們?cè)?awk 或 sed 的命令參數(shù)中, 調(diào)用之前設(shè)定的一些變量時(shí),常會(huì)問(wèn)及為何不能的問(wèn)題。
要解決這些問(wèn)題,關(guān)鍵點(diǎn)就是:區(qū)分出 shell meta 與 command meta
前面我們提到的那些 meta,都是在 command line 中有特殊用途的, 比方說(shuō) {} 就是將一系列的 command line 置于不具名的函數(shù)中執(zhí)行 (可簡(jiǎn)單視為 command block), 但是,awk 卻需要用 {} 來(lái)區(qū)分出 awk 的命令區(qū)段 (BEGIN,MAIN,END). 若你在 command line 中如此輸入:
$ awk {print $0} 1.txt
由于 {} 在 shell 中并沒(méi)有關(guān)閉,那 shell 就將 {print $0} 視為 command block, 但同時(shí)沒(méi)有;符號(hào)作命令分隔,因此,就出現(xiàn) awk 語(yǔ)法錯(cuò)誤結(jié)果。
要解決之,可用 hard quote:
awk '{print $0}'
上面的 hard quote 應(yīng)好理解,就是將原來(lái)的 {、、$、} 這幾個(gè) shell meta 關(guān)閉, 避免掉在 shell 中遭到處理,而完整的成為 awk 的參數(shù)中 command meta。
Note:
awk 中使用的 $0 是 awk 中內(nèi)建的 field nubmer,而非 awk 的變量, awk 自身的變量無(wú)需使用 $。
要是理解了 hard quote 的功能,在來(lái)理解 soft quote 與 escape 就不難:
awk "{print \$0}" 1.txt
awk \{print \$0\} 1.txt
然而,若要你改變 awk 的 $0 的 0 值是從另一個(gè) shell 變量中讀進(jìn)呢? 比方說(shuō):已有變量 $A 的值是 0, 那如何在command line中解決 awk 的 $$A 呢? 你可以很直接否定掉 hard quote 的方案:
$ awk '{print $$A}' 1.txt
那是因?yàn)?$A 的 $ 在 hard quote 中是不能替換變量的。
聰明的讀者 (如你!),經(jīng)過(guò)本章的學(xué)習(xí),我想,你應(yīng)該可以理解為 為何我們可以使用如下操作了吧:
A=0
awk "{print \$$A}" 1.txt
awk \{print\ \$$A\} 1.txt
awk '{print $'$A'}' 1.txt
awk '{print $'"$A"'}' 1.txt
或許,你能給出更多方案... ^_^
更多練習(xí):
read a #輸入: abc
echo "$a" #只輸出abc
原因: 變量 a 的值,從終端輸入的值是以 IFS 開頭,而這些 IFS 將被 shell 解釋器忽略 (trim)。 應(yīng)該與 shell 解釋器分詞的規(guī)則有關(guān);
read a #輸入:\ \ \ abc
echo "$a" #只輸出abc
需要將空格字符轉(zhuǎn)義
Note:
IFS Internal field separators, normally space, tab, and newline (see Blank Interpretation section). ...... Blank Interpretation After parameter and command substitution, the results of substitution
are scanned for internal field separator characters (those found in IFS) and split into distinct arguments where such characters are found. Explicit null arguments (""or'') are retained.
Implicit null arguments(those resulting from parameters that have no values) are removed. (refre to: man sh)
解決思路:
若你還是不理解,那來(lái)驗(yàn)證一下下面這個(gè)例子:
$ A=" abc"
$ echo $A
abc
$ echo "$A" #note1
abc
$ old_IFS=$IFS
$ IFS=;
$ echo $A
abc
$ IFS=$old_IFS
$ echo $A
abc
Note:
這里是用 soft quoting 將里面的 space 關(guān)閉,使之不是 meta(IFS), 而是一個(gè) literal(white space);
- IFS=; 意義是將 IFS 設(shè)置為空字符,因?yàn)? 是 shell 的元字符 (meta);
問(wèn)題二:為什么多做了幾個(gè)分號(hào),我想知道為什么會(huì)出現(xiàn)空格呢?
$ a=";;;test"
$ IFS=";"
$ echo $a
test
$ a=" test"
$ echo $a
test
$ IFS=" "
$ echo $a
test
解答:
這個(gè)問(wèn)題,出在IFS=;上。 因?yàn)檫@個(gè);在問(wèn)題一中的 command line 上是一個(gè) meta, 并非";"符號(hào)本身。 因此,IFS=;是將 IFS 設(shè)置為 null charactor (不是 space、tab、newline)。
要不是試試下面這個(gè)代碼片段:
$ old_IFS=$IFS
$ read A
;a;b;c
$ echo $A
;a;b;c
$ IFS=";" #Note2
$ echo $A
a b c
Note:
要關(guān)閉
;可用";"或者';'或者\;。
思考問(wèn)題二:文本處理:讀文件時(shí),如何保證原汁原味。
cat file | while read i
do
echo $i
done
文件 file 的行中包含若干空,經(jīng)過(guò) read 只保留不重復(fù)的空格。 如何才能所見(jiàn)即所得。
cat file | while read i
do
echo "X${i}X"
done
從上面的輸出,可以看出 read,讀入是按整行讀入的; 不能原汁原味的原因:
echo $i的解析過(guò)程中,首先將 $i 替換為字符串, 然后對(duì) echo 字符串中字符串分詞,然后命令重組,輸出結(jié)果; 在分詞,與命令重組時(shí),可能導(dǎo)致多個(gè)相鄰的 IFS 轉(zhuǎn)化為一個(gè);cat file | while read i
do
echo "$i"
done
以上代碼可以解決原因 2 中的,command line 的分詞和重組導(dǎo)致 meta 字符丟失; 但仍然解決不了原因 1 中,read 讀取行時(shí),忽略行起始的 IFS meta 字符。
回過(guò)頭來(lái)看上面這個(gè)問(wèn)題:為何要原汁原味呢? cat 命令就是原汁原味的,只是 shell 的 read、echo 導(dǎo)致了某些 shell 的 meta 字符丟失;
如果只是 IFS meta 的丟失,可以采用如下方式: 將 IFS 設(shè)置為 null,即IFS=;, 在此再次重申此處;是 shell 的 meta 字符, 而不是 literal 字符; 因此要使用 literal 的 ;應(yīng)該是\; 或者關(guān)閉 meta 的 (soft/hard) quoting 的";"或者';'。
因此上述的解決方案是:
old_IFS=$IFS
IFS=; #將IFS設(shè)置為null
cat file | while read i
do
echo "$i"
done
IFS=old_IFS #恢復(fù)IFS的原始值
現(xiàn)在,回過(guò)頭來(lái)看這個(gè)問(wèn)題,為什么會(huì)有這個(gè)問(wèn)題呢; 其本源的問(wèn)題應(yīng)該是沒(méi)有找到解決原始問(wèn)題的最合適的方法, 而是采取了一個(gè)迂回的方式來(lái)解決了問(wèn)題;
因此,我們應(yīng)該回到問(wèn)題的本源,重新審視一下,問(wèn)題的本質(zhì)。 如果要精準(zhǔn)的獲取文件的內(nèi)容,應(yīng)該使用 od 或者 hexdump 會(huì)更好些。