上一篇文章參見 第二節(jié):bash編程易犯的錯(cuò)誤。
和大多數(shù) Shell 一樣,Bash 支持依次讀取單個(gè)命令行參數(shù)的語法。不過這并是$*或者$@,這兩種寫法都不正確,它們只能得到完整的參數(shù)列表,并非單獨(dú)的一個(gè)個(gè)參數(shù)。
正確的語法是(沒錯(cuò)要加上引號):
for arg in "$@"
# 或者更簡單的寫法
for arg
在腳本中遍歷所有參數(shù)是一個(gè)再普遍不過的需求,所以 for arg 默認(rèn)等價(jià)于 for arg in “$@”。$@使用雙引號后就有特殊的魔力,每個(gè)參數(shù)展開后成為一個(gè)獨(dú)立的單詞。(”$@”等價(jià)于”$1” “$2” “$3” …)
下面是一個(gè)錯(cuò)誤的例子:
for x in $*; do echo "parameter: '$x'" done 執(zhí)行的結(jié)果為: $ ./myscript 'arg 1' arg2 arg3 parameter: 'arg' parameter: '1' parameter: 'arg2' parameter: 'arg3'
正確的寫法:
for x in "$@"; do echo "parameter: '$x'" done 執(zhí)行的結(jié)果為: $ ./myscript 'arg 1' arg2 arg3 parameter: 'arg 1' parameter: 'arg2' parameter: 'arg3'
上面正確的例子中,第一個(gè)參數(shù)’arg 1’在展開后依然是一個(gè)獨(dú)立的單詞,而不會(huì)被拆分成兩個(gè)。
25. function foo()
這種寫法不一定能夠兼容所有 shell,兼容的寫法是:
foo() { ... }
26. echo “~”
波浪號展開(Tilde expansion)僅當(dāng)~沒有引號的時(shí)候發(fā)生,在上面的例子中,只會(huì)向標(biāo)準(zhǔn)輸出打印~符號,而不是當(dāng)前用戶的家目錄路徑。
當(dāng)用引號將路徑參數(shù)引起來時(shí),如果要用引號將相對于家目錄的路徑引起來時(shí),推薦使用 $HOME 而不是 ~, 假如 $HOME 目錄是”/home/my photos”,路徑中包含空格。
下面是幾組例子:
"~/dir with spaces" # expands to "~/dir with spaces" ~"/dir with spaces" # expands to "~/dir with spaces" ~/"dir with spaces" # expands to "/home/my photos/dir with spaces" "$HOME/dir with spaces" # expands to "/home/my photos/dir with spaces"
27. local varname=$(command)
當(dāng)在函數(shù)中聲明局部變量時(shí),local作為一個(gè)獨(dú)立的命令,這種奇特的行為有時(shí)候可能會(huì)導(dǎo)致困擾。比如,當(dāng)你想要捕獲命令替換的返回碼時(shí),你就不能這樣做。local命令的返回碼會(huì)覆蓋它。
這種情況下,你只能分成兩行寫:
local varname varname=$(command) rc=$?
28. export foo=~/bar
export 與 local 命令一樣,并不是賦值語句的一部分。因此,在有些 Shell 下(比如Bash),export foo=~/bar會(huì)展開,但是有些(比如 dash)卻不行。
下面是兩種比較健壯的寫法:
foo=~/bar; export foo # Right! export foo="$HOME/bar" # Right!
29. sed ‘s/$foo/good bye/’
單引號內(nèi)部不會(huì)展開 $foo變量,在這里可以換成雙引號:
foo="hello"; sed "s/$foo/good bye/"
但是要注意,如果你使用了雙引號,就需要考慮更多轉(zhuǎn)義的事情,具體可以看Quotes這一頁。.
30. tr [A-Z] [a-z]
這里至少有三個(gè)問題。第一個(gè)問題是, [A-Z] 和 [a-z] 會(huì)被 shell 認(rèn)為是通配符。如果在當(dāng)前目錄下沒用文件名為單個(gè)字母的文件,這個(gè)命令似乎能正確執(zhí)行,否則會(huì)錯(cuò)誤地執(zhí)行,也許你會(huì)在周末耗費(fèi)許多小時(shí)來修復(fù)這個(gè)問題。
第二個(gè)問題是,這不是 tr 命令正確的寫法,實(shí)際上,上面的命令會(huì)把[轉(zhuǎn)換成[,將任意大寫字符轉(zhuǎn)換成對應(yīng)的小寫字符,將]轉(zhuǎn)換成],所以你根本不需要加上括號,這樣第一個(gè)問題就可以解決了。
第三個(gè)問題是,上面的命令執(zhí)行結(jié)果依賴于當(dāng)前的 locale,A-Z 或者 a-z 不一定會(huì)代表26個(gè) ASCII 字母。實(shí)際上,在一些語言環(huán)境下,z 位于字母表的中間位置。這個(gè)問題的解法,取決于你希望發(fā)生的行為是哪一種。
如果你僅希望改變26個(gè)英文字母的大小寫(強(qiáng)制 locale為 C):
LC_COLLATE=C tr A-Z a-z 如果你希望根據(jù)實(shí)際的語言環(huán)境來轉(zhuǎn)換: tr '[:upper:]' '[:lower:]'
31. ps ax | grep gedit
這里的根本問題是正在運(yùn)行的進(jìn)程名稱,本質(zhì)上是不可靠的。可能會(huì)有多個(gè)合法的gedit進(jìn)程,也有可能是別的東西偽裝成gedit進(jìn)程(改變執(zhí)行命令名稱是一件簡單的事情 ),更多細(xì)節(jié)可以看ProcessManagement這一篇文章。
執(zhí)行以上命令,往往會(huì)在結(jié)果中包含 grep 進(jìn)程:
# ps ax | grep gedit 10530 ? S 6:23 gedit 32118 pts/0 R+ 0:00 grep gedit 這個(gè)時(shí)候,需要過濾多余的結(jié)果: # ps ax | grep -v grep | grep gedit 上面的寫法比較丑陋,另外一種方法是: # ps ax | grep [g]edit
32. printf “$foo”
如果$foo 變量的值中包括或者%符號,上面命令的執(zhí)行結(jié)果可能會(huì)出乎你的意料之外。
下面是正確的寫法:
printf %s "$foo" printf '%s ' "$foo"
33. for i in {1..$n}
Bash的命令解釋器會(huì)優(yōu)先展開大括號,所以這時(shí)大括號{}表達(dá)式里面看到的是文字上的$n(沒有展開)。$n 不是一個(gè)數(shù)值,所以這里的大括號{}并不會(huì)展開成數(shù)字列表。可見,這導(dǎo)致很難使用大括號來展開大小只能在運(yùn)行時(shí)才知道的列表。
可以用下面的方法:
for ((i=1; i< =n; i++)); do ... done
注:之前我也有寫過一篇文章來介紹這個(gè)問題:Shell生成數(shù)字序列。
34. if [[ $foo = $bar ]]
在[[內(nèi)部,當(dāng)=號右邊的值沒有用引號引起來,bash 會(huì)將它當(dāng)作模式來匹配,而不是一個(gè)簡單的字符串。所以,在上面的例子中 ,如果 bar 的值是一個(gè)*號,執(zhí)行的結(jié)果永遠(yuǎn)是 true。
所以,如果你想檢查兩側(cè)的字符串是否相同,等號右側(cè)的值一定要用引號引起來。
if [[ $foo = "$bar" ]]
如果你確實(shí)要執(zhí)行模式匹配,聰明的做法是取一個(gè)更加有意義的變量名(例如$patt),或者加上注釋說明。
35. if [[ $foo =~ ‘some RE’ ]]
同上,如果=~號右側(cè)的值加上引號,它會(huì)散失特殊的正則表達(dá)式含義,而變成一個(gè)普通的字符串。
如果你想使用一個(gè)長的或者復(fù)雜的正則表達(dá)式,避免大量的反斜杠轉(zhuǎn)義,建議把它放在一個(gè)變量中:
re='some RE' if [[ $foo =~ $re ]]
由于篇幅限制,本系列文章會(huì)分成多篇文章,最后一篇參見 第四節(jié):Bash編程易犯的錯(cuò)誤。