Linux Shell Bash 陷阱
Turandot: Gli enigmi sono tre, la morte una!
Caleph: No, no! Gli enigmi sono tre, una la vita!
Puccini
將保留字或特殊字符聲明為變量名.
1 case=value0 # 引發(fā)錯(cuò)誤.
2 23skidoo=value1 # 也會(huì)引發(fā)錯(cuò)誤.
3 # 以數(shù)字開(kāi)頭的變量名是被shell保留使用的.
4 # 試試_23skidoo=value1. 以下劃線開(kāi)頭的變量就沒(méi)問(wèn)題.
5
6 # 然而 . . . 如果只用一個(gè)下劃線作為變量名就不行.
7 _=25
8 echo $_ # $_是一個(gè)特殊變量, 代表最后一個(gè)命令的最后一個(gè)參數(shù).
9
10 xyz((!*=value2 # 引起嚴(yán)重的錯(cuò)誤.
11 # Bash3.0之后, 標(biāo)點(diǎn)不能出現(xiàn)在變量名中.
使用連字符或其他保留字符來(lái)做變量名(或函數(shù)名).
1 var-1=23
2 # Use 'var_1' instead.
3
4 function-whatever () # 錯(cuò)誤
5 # 使用'function_whatever ()'來(lái)代替.
6
7
8 # Bash3.0之后, 標(biāo)點(diǎn)不能出現(xiàn)在函數(shù)名中.
9 function.whatever () # 錯(cuò)誤
10 # 使用'functionWhatever ()'來(lái)代替.
讓變量名與函數(shù)名相同. 這會(huì)使得腳本的可讀性變得很差.
1 do_something ()
2 {
3 echo "This function does something with \"$1\"."
4 }
5
6 do_something=do_something
7
8 do_something do_something
9
10 # 這么做是合法的, 但是會(huì)讓人混淆.
不合時(shí)宜的使用
空白字符. 與其它編程語(yǔ)言相比, Bash非常講究空白字符的使用.
1 var1 = 23 # 'var1=23'才是正確的.
2 # 對(duì)于上邊這一行來(lái)說(shuō), Bash會(huì)把"var1"當(dāng)作命令來(lái)執(zhí)行,
3 # "="和"23"會(huì)被看作"命令""var1"的參數(shù).
4
5 let c = $a - $b # 'let c=$a-$b'或'let "c = $a - $b"'才是正確的.
6
7 if [ $a -le 5] # if [ $a -le 5 ] 是正確的.
8 # if [ "$a" -le 5 ] 這么寫(xiě)更好.
9 # [[ $a -le 5 ]] 也行.
在
大括號(hào)包含的代碼塊中, 最后一條命令沒(méi)有以分號(hào)結(jié)尾.
1 { ls -l; df; echo "Done." }
2 # bash: syntax error: unexpected end of file
3
4 { ls -l; df; echo "Done."; }
5 # ^ ### 最后的這條命令必須以分號(hào)結(jié)尾.
假定未初始化的變量(賦值前的變量)被"清0". 事實(shí)上, 未初始化的變量值為"null", 而不是0.
1 #!/bin/bash
2
3 echo "uninitialized_var = $uninitialized_var"
4 # uninitialized_var =
混淆測(cè)試符號(hào)=和-eq. 請(qǐng)記住, =用于比較字符變量, 而-eq用來(lái)比較整數(shù).
1 if [ "$a" = 273 ] # $a是整數(shù)還是字符串?
2 if [ "$a" -eq 273 ] # $a為整數(shù).
3
4 # 有些情況下, 即使你混用-eq和=, 也不會(huì)產(chǎn)生錯(cuò)誤的結(jié)果.
5 # 然而 . . .
6
7
8 a=273.0 # 不是一個(gè)整數(shù).
9
10 if [ "$a" = 273 ]
11 then
12 echo "Comparison works."
13 else
14 echo "Comparison does not work."
15 fi # Comparison does not work.
16
17 # 與a=" 273"和a="0273"相同.
18
19
20 # 類(lèi)似的, 如果對(duì)非整數(shù)值使用"-eq"的話, 就會(huì)產(chǎn)生問(wèn)題.
21
22 if [ "$a" -eq 273.0 ]
23 then
24 echo "a = $a"
25 fi # 因?yàn)楫a(chǎn)生了錯(cuò)誤消息, 所以退出.
26 # test.sh: [: 273.0: integer expression expected
誤用了
字符串比較操作符.
例子 31-1. 數(shù)字比較與字符串比較并不相同
1 #!/bin/bash
2 # bad-op.sh: 嘗試一下對(duì)整數(shù)使用字符串比較.
3
4 echo
5 number=1
6
7 # 下面的"while循環(huán)"有兩個(gè)錯(cuò)誤:
8 #+ 一個(gè)比較明顯, 而另一個(gè)比較隱蔽.
9
10 while [ "$number" < 5 ] # 錯(cuò)! 應(yīng)該是: while [ "$number" -lt 5 ]
11 do
12 echo -n "$number "
13 let "number += 1"
14 done
15 # 如果你企圖運(yùn)行這個(gè)錯(cuò)誤的腳本, 那么就會(huì)得到一個(gè)錯(cuò)誤消息:
16 #+ bad-op.sh: line 10: 5: No such file or directory
17 # 在單中括號(hào)結(jié)構(gòu)([ ])中, "<"必須被轉(zhuǎn)義.
18 #+ 即便如此, 比較兩個(gè)整數(shù)還是錯(cuò)誤的.
19
20
21 echo "---------------------"
22
23
24 while [ "$number" \< 5 ] # 1 2 3 4
25 do #
26 echo -n "$number " # 看起來(lái)*好像可以工作, 但是 . . .
27 let "number += 1" #+ 事實(shí)上是比較ASCII碼,
28 done #+ 而不是整數(shù)比較.
29
30 echo; echo "---------------------"
31
32 # 這么做會(huì)產(chǎn)生問(wèn)題. 比如:
33
34 lesser=5
35 greater=105
36
37 if [ "$greater" \< "$lesser" ]
38 then
39 echo "$greater is less than $lesser"
40 fi # 105 is less than 5
41 # 事實(shí)上, 在字符串比較中(按照ASCII碼的順序)
42 #+ "105"小于"5".
43
44 echo
45
46 exit 0
有時(shí)候在"test"中括號(hào)([ ])結(jié)構(gòu)里的變量需要被引用起來(lái)(雙引號(hào)). 如果不這么做的話, 可能會(huì)引起不可預(yù)料的結(jié)果. 請(qǐng)參考
例子 7-6,
例子 16-5, 和
例子 9-6.
腳本中的命令可能會(huì)因?yàn)槟_本宿主不具備相應(yīng)的運(yùn)行權(quán)限而導(dǎo)致運(yùn)行失敗, 如果用戶在命令行中就不能調(diào)用這個(gè)命令的話, 那么即使把它放到腳本中來(lái)運(yùn)行, 也還是會(huì)失敗. 這時(shí)可以通過(guò)修改命令的屬性來(lái)解決這個(gè)問(wèn)題, 有時(shí)候甚至要給它設(shè)置suid位(當(dāng)然, 要以root身份來(lái)設(shè)置).
試圖使用-作為重定向操作符(事實(shí)上它不是), 通常都會(huì)導(dǎo)致令人不快的結(jié)果.
1 command1 2> - | command2 # 試圖將command1的錯(cuò)誤輸出重定向到一個(gè)管道中...
2 # ...不會(huì)工作.
3
4 command1 2>& - | command2 # 也沒(méi)效果.
5
6 感謝, S.C.
使用Bash
2.0或更高版本的功能, 可以在產(chǎn)生錯(cuò)誤信息的時(shí)候, 引發(fā)修復(fù)動(dòng)作. 但是比較老的Linux機(jī)器默認(rèn)安裝的可能是Bash 1.XX.
1 #!/bin/bash
2
3 minimum_version=2
4 # 因?yàn)镃het Ramey經(jīng)常給Bash添加一些新的特征,
5 # 所以你最好將$minimum_version設(shè)置為2.XX, 3.XX, 或是其他你認(rèn)為比較合適的值.
6 E_BAD_VERSION=80
7
8 if [ "$BASH_VERSION" \< "$minimum_version" ]
9 then
10 echo "This script works only with Bash, version $minimum or greater."
11 echo "Upgrade strongly recommended."
12 exit $E_BAD_VERSION
13 fi
14
15 ...
在非Linux機(jī)器上的Bourne shell腳本(#!/bin/sh)中使用Bash特有的功能, 可能會(huì)引起不可預(yù)料的行為. Linux系統(tǒng)通常都會(huì)把bash別名化為sh, 但是在一般的UNIX機(jī)器上卻不一定會(huì)這么做.
使用Bash未文檔化的特征, 將是一種危險(xiǎn)的舉動(dòng). 本書(shū)之前的幾個(gè)版本就依賴(lài)一個(gè)這種"特征", 下面說(shuō)明一下這個(gè)"特征", 雖然
exit或
return所能返回的最大正值為255, 但是并沒(méi)有限制我們使用負(fù)整數(shù). 不幸的是, Bash 2.05b之后的版本, 這個(gè)漏洞消失了. 請(qǐng)參考
例子 23-9.
一個(gè)帶有DOS風(fēng)格換行符(\r\n)的腳本將會(huì)運(yùn)行失敗, 因?yàn)?!/bin/bash\r\n是不合法的, 與我們所期望的#!/bin/bash\n不同. 解決辦法就是將這個(gè)腳本轉(zhuǎn)換為UNIX風(fēng)格的換行符.
1 #!/bin/bash
2
3 echo "Here"
4
5 unix2dos $0 # 腳本先將自己改為DOS格式.
6 chmod 755 $0 # 更改可執(zhí)行權(quán)限.
7 # 'unix2dos'會(huì)刪除可執(zhí)行權(quán)限.
8
9 ./$0 # 腳本嘗試再次運(yùn)行自己.
10 # 但它作為一個(gè)DOS文件, 已經(jīng)不能運(yùn)行了.
11
12 echo "There"
13
14 exit 0
以#!/bin/sh開(kāi)頭的Bash腳本, 不能在完整的Bash兼容模式下運(yùn)行. 某些Bash特定的功能可能會(huì)被禁用. 如果腳本需要完整的訪問(wèn)所有Bash專(zhuān)有擴(kuò)展, 那么它需要使用#!/bin/bash作為開(kāi)頭.
如果在
here document中,
結(jié)尾的limit string之前加上空白字符的話, 將會(huì)導(dǎo)致腳本的異常行為.
腳本不能將變量export到它的
父進(jìn)程(即調(diào)用這個(gè)腳本的shell), 或父進(jìn)程的環(huán)境中. 就好比我們?cè)谏飳W(xué)中所學(xué)到的那樣, 子進(jìn)程只會(huì)繼承父進(jìn)程, 反過(guò)來(lái)則不行.
1 WHATEVER=/home/bozo
2 export WHATEVER
3 exit 0
bash$ echo $WHATEVER
bash$
可以確定的是, 即使回到命令行提示符, 變量$WHATEVER仍然沒(méi)有被設(shè)置.
在
子shell中設(shè)置和操作變量之后, 如果嘗試在子shell作用域之外使用同名變量的話, 將會(huì)產(chǎn)生令人不快的結(jié)果.
例子 31-2. 子shell缺陷
1 #!/bin/bash
2 # 子shell中的變量缺陷.
3
4 outer_variable=outer
5 echo
6 echo "outer_variable = $outer_variable"
7 echo
8
9 (
10 # 開(kāi)始子shell
11
12 echo "outer_variable inside subshell = $outer_variable"
13 inner_variable=inner # Set
14 echo "inner_variable inside subshell = $inner_variable"
15 outer_variable=inner # 會(huì)修改全局變量么?
16 echo "outer_variable inside subshell = $outer_variable"
17
18 # 如果將變量'導(dǎo)出'會(huì)產(chǎn)生不同的結(jié)果么?
19 # export inner_variable
20 # export outer_variable
21 # 試試看.
22
23 # 結(jié)束子shell
24 )
25
26 echo
27 echo "inner_variable outside subshell = $inner_variable" # 未設(shè)置.
28 echo "outer_variable outside subshell = $outer_variable" # 未修改.
29 echo
30
31 exit 0
32
33 # 如果你打開(kāi)第19和第20行的注釋會(huì)怎樣?
34 # 會(huì)產(chǎn)生不同的結(jié)果么? (譯者注: 小提示, 第18行的'導(dǎo)出'都加上引號(hào)了.)
將echo的輸出通過(guò)
管道傳遞給
read命令可能會(huì)產(chǎn)生不可預(yù)料的結(jié)果. 在這種情況下, read命令的行為就好像它在子shell中運(yùn)行一樣. 可以使用
set命令來(lái)代替(就好像
例子 11-17一樣).
例子 31-3. 將echo的輸出通過(guò)管道傳遞給read命令
1 #!/bin/bash
2 # badread.sh:
3 # 嘗試使用'echo'和'read'命令
4 #+ 非交互的給變量賦值.
5
6 a=aaa
7 b=bbb
8 c=ccc
9
10 echo "one two three" | read a b c
11 # 嘗試重新給變量a, b, 和c賦值.
12
13 echo
14 echo "a = $a" # a = aaa
15 echo "b = $b" # b = bbb
16 echo "c = $c" # c = ccc
17 # 重新賦值失敗.
18
19 # ------------------------------
20
21 # 試試下邊這種方法.
22
23 var=`echo "one two three"`
24 set -- $var
25 a=$1; b=$2; c=$3
26
27 echo "-------"
28 echo "a = $a" # a = one
29 echo "b = $b" # b = two
30 echo "c = $c" # c = three
31 # 重新賦值成功.
32
33 # ------------------------------
34
35 # 也請(qǐng)注意, echo到'read'的值只會(huì)在子shell中起作用.
36 # 所以, 變量的值*只*會(huì)在子shell中被修改.
37
38 a=aaa # 重新開(kāi)始.
39 b=bbb
40 c=ccc
41
42 echo; echo
43 echo "one two three" | ( read a b c;
44 echo "Inside subshell: "; echo "a = $a"; echo "b = $b"; echo "c = $c" )
45 # a = one
46 # b = two
47 # c = three
48 echo "-----------------"
49 echo "Outside subshell: "
50 echo "a = $a" # a = aaa
51 echo "b = $b" # b = bbb
52 echo "c = $c" # c = ccc
53 echo
54
55 exit 0
事實(shí)上, 也正如Anthony Richardson指出的那樣, 通過(guò)管道將輸出傳遞到任何循環(huán)中, 都會(huì)引起類(lèi)似的問(wèn)題.
1 # 循環(huán)的管道問(wèn)題.
2 # 這個(gè)例子由Anthony Richardson編寫(xiě),
3 #+ 由Wilbert Berendsen補(bǔ)遺.
4
5
6 foundone=false
7 find $HOME -type f -atime +30 -size 100k |
8 while true
9 do
10 read f
11 echo "$f is over 100KB and has not been accessed in over 30 days"
12 echo "Consider moving the file to archives."
13 foundone=true
14 # ------------------------------------
15 echo "Subshell level = $BASH_SUBSHELL"
16 # Subshell level = 1
17 # 沒(méi)錯(cuò), 現(xiàn)在是在子shell中運(yùn)行.
18 # ------------------------------------
19 done
20
21 # 變量foundone在這里肯定是false,
22 #+ 因?yàn)樗窃谧觭hell中被設(shè)置為true的.
23 if [ $foundone = false ]
24 then
25 echo "No files need archiving."
26 fi
27
28 # =====================現(xiàn)在, 下邊是正確的方法:=================
29
30 foundone=false
31 for f in $(find $HOME -type f -atime +30 -size 100k) # 這里沒(méi)使用管道.
32 do
33 echo "$f is over 100KB and has not been accessed in over 30 days"
34 echo "Consider moving the file to archives."
35 foundone=true
36 done
37
38 if [ $foundone = false ]
39 then
40 echo "No files need archiving."
41 fi
42
43 # ==================這里是另一種方法==================
44
45 # 將腳本中讀取變量的部分放到一個(gè)代碼塊中,
46 #+ 這樣一來(lái), 它們就能在相同的子shell中共享了.
47 # 感謝, W.B.
48
49 find $HOME -type f -atime +30 -size 100k | {
50 foundone=false
51 while read f
52 do
53 echo "$f is over 100KB and has not been accessed in over 30 days"
54 echo "Consider moving the file to archives."
55 foundone=true
56 done
57
58 if ! $foundone
59 then
60 echo "No files need archiving."
61 fi
62 }
一個(gè)相關(guān)的問(wèn)題: 當(dāng)你嘗試將tail -f的stdout通過(guò)管道傳遞給
grep時(shí), 會(huì)產(chǎn)生問(wèn)題.
1 tail -f /var/log/messages | grep "$ERROR_MSG" >> error.log
2 # "error.log"文件將不會(huì)寫(xiě)入任何東西.
在腳本中使用"suid"命令是非常危險(xiǎn)的, 因?yàn)檫@會(huì)危及系統(tǒng)安全.
[1]使用shell腳本來(lái)編寫(xiě)CGI程序是值得商榷的. 因?yàn)镾hell腳本的變量不是"類(lèi)型安全"的, 當(dāng)CGI被關(guān)聯(lián)的時(shí)候, 可能會(huì)產(chǎn)生令人不快的行為. 此外, 它還很難抵擋住"破解的考驗(yàn)".
Bash不能正確的處理
雙斜線(//)字符串.
在Linux或BSD上編寫(xiě)的Bash腳本, 可能需要修改一下, 才能使它們運(yùn)行在商業(yè)的UNIX(或Apple OSX)機(jī)器上. 這些腳本通常都使用GNU命令和過(guò)濾工具, GNU工具通常都比一般的UNIX上的同類(lèi)工具更加強(qiáng)大. 這方面的一個(gè)非常明顯的例子就是, 文本處理工具
tr.
危險(xiǎn)正在接近你 --
小心, 小心, 小心, 小心.
許多勇敢的心都在沉睡.
所以一定要小心 --
小心.
A.J. Lamb and H.W. Petrie
注意事項(xiàng)
[1]給腳本設(shè)置suid權(quán)限是沒(méi)用的.