上一篇文章參見 第一節(jié):bash編程易犯的錯(cuò)誤。
13. cat file | sed s/foo/bar/ > file
你不應(yīng)該在一個(gè)管道中,從一個(gè)文件讀的同時(shí),再往相同的文件里面寫,這樣的后果是未知的。
你可以為此創(chuàng)建一個(gè)臨時(shí)文件,這種做法比較安全可靠:
# sed 's/foo/bar/g' file > tmpfile && mv tmpfile file
或者,如果你用得是 gnu Sed 4.x 以上的版本,可以使用-i 選項(xiàng)即時(shí)修改文件的內(nèi)容:
# sed -i 's/foo/bar/g' file
14. echo $foo
這種看似無害的命令往往會給初學(xué)者千萬極大的困擾,他們會懷疑是不是因?yàn)?$foo 變量的值是錯(cuò)誤的。事實(shí)卻是因?yàn)椋?foo 變量在這里沒有使用雙引號,所以在解析的時(shí)候會進(jìn)行單詞拆分和文件名展開,最終導(dǎo)致執(zhí)行結(jié)果與預(yù)期大相徑庭:
msg="Please enter a file name of the form *.zip" echo $msg
這里整句話會被拆分成單詞,然后其中的通配符會被展開,例如*.zip。當(dāng)你的用戶看到如下的結(jié)果時(shí),他們會怎樣想:
Please enter a file name of the form freenfss.zip lw35nfss.zip 再舉一個(gè)例子(假設(shè)當(dāng)前目錄下有以 .zip 結(jié)尾的文件): var="*.zip" # var 包括一個(gè)星號,一個(gè)點(diǎn)號和 zip echo "$var" # 輸出 *.zip echo $var # 輸出所有以 .zip 結(jié)尾的文件
實(shí)際上,這里使用 echo 命令并不是絕對的安全。例如,當(dāng)變量的值包含-n時(shí),echo 會認(rèn)為它是一個(gè)合法的選項(xiàng)而不是要輸出的內(nèi)容(當(dāng)然如果你能夠保證不會有-n 這種值,可以放心地使用 echo 命令)。
完全可靠的打印變量值的方法是使用 printf:
printf "%s " "$foo"
15. $foo=bar
略過
16. foo = bar
當(dāng)賦值時(shí),等號兩邊是不允許出現(xiàn)空格的,這同 C 語言不一樣。當(dāng)你寫下 foo = bar 時(shí),Shell 會將該命令解析成三個(gè)單詞,然后第一個(gè)單詞 foo 會被認(rèn)為是一個(gè)命令,后面的內(nèi)容會被當(dāng)作命令參數(shù)。
同樣地,下面的寫法也是錯(cuò)誤的:
foo= bar # WRONG! foo =bar # WRONG! $foo = bar; # COMPLETELY WRONG!
正確的寫法應(yīng)該是這樣的:
foo=bar # Right. foo="bar" # more Right.
17. echo 或者可以使用雙引號,它也可以跨越多行,而且因?yàn)?echo 命令是內(nèi)置命令,相同情況下它會更加高效:
echo "Hello world How's it going?"
18. su -c ‘some command’
這種寫法“幾乎”是正確的。問題是,在許多平臺上,su 支持 -c 參數(shù),但是它不一定是你認(rèn)為的。比如,在 OpenBSD 平臺上你這樣執(zhí)行會出錯(cuò):
$ su -c 'echo hello' su: only the superuser may specify a login class 在這里,-c是用于指定login-class。如果你想要傳遞 -c 'some command' 給 shell,最好在之前顯示地指定 username: $ su root -c 'some command' # Now it's right.
19. cd /foo; bar
如果你不檢查 cd 命令執(zhí)行是否成功,你可以會在錯(cuò)誤的目錄下執(zhí)行 bar 命令,這有可能會帶來災(zāi)難,比如 bar 命令是 rm -rf *。
你必須經(jīng)常檢查 cd 命令執(zhí)行是否有錯(cuò)誤,簡單的做法是:
cd /foo && bar 如果在 cd 命令后有多個(gè)命令,你可以選擇這樣寫: cd /foo || exit 1 bar baz bat ... # Lots of commands.
出錯(cuò)時(shí),cd 命令會報(bào)告無法改變當(dāng)前目錄,同時(shí)將錯(cuò)誤消息輸出到標(biāo)準(zhǔn)錯(cuò)誤,例如”bash: cd: /foo: No such file or directory”。如果你想要在標(biāo)準(zhǔn)輸出同時(shí)輸出自定義的錯(cuò)誤提示,可以使用復(fù)合命令(command grouping):
cd /net || { echo "Can't read /net. make sure you've logged in to the Samba network, and try again."; exit 1; } do_stuff more_stuff
注意,在{號和 echo 之間需要有一個(gè)空格,同時(shí)}之前要加上分號。
順便提一下,如果你要在腳本里頻繁改變當(dāng)前目錄,可以看看 pushd/popd/dirs 等命令,可能你在代碼里面寫的 cd/pwd 命令都是沒有必要的。
說到這,比較下下面兩種寫法:
find ... -type d -print0 | while IFS= read -r -d '' subdir; do here=$PWD cd "$subdir" && whatever && ... cd "$here" done find ... -type d -print0 | while IFS= read -r -d '' subdir; do (cd "$subdir" || exit; whatever; ...) done
下面的寫法,在循環(huán)中 fork 了一個(gè)子 shell 進(jìn)程,子 shell 進(jìn)程中的 cd 命令僅會影響當(dāng)前 shell的環(huán)境變量,所以父進(jìn)程中的環(huán)境命令不會被改變;當(dāng)執(zhí)行到下一次循環(huán)時(shí),無論之前的 cd 命令有沒有執(zhí)行成功,我們會回到相同的當(dāng)前目錄。這種寫法相較前面的用法,代碼更加干凈。
20. [ bar == “$foo” ]
正確的用法:
[ bar = "$foo" ] && echo yes [[ bar == $foo ]] && echo yes
21. for i in {1..10}; do ./something &; done
你不應(yīng)該在&后面添加分號,刪除它:
for i in {1..10}; do ./something & done 或者改成多行的形式: for i in {1..10}; do ./something & done
&和分號一樣也可以用作命令終止符,所以你不要將兩個(gè)混用到一起。一般情況下,分號可以被換行符替換,但是不是所有的換行符都可以用分號替換。
22. cmd1 && cmd2 || cmd3
有些人喜歡把&&和||作為if…then…else…fi 的簡寫語法,在多數(shù)情況下,這種寫法沒有問題。例如:
[[ -s $errorlog ]] && echo "Uh oh, there were some errors." || echo "Successful."
但是,這種結(jié)構(gòu)并不是在所有情況下都完全等價(jià)于 if…fi 語法。這是因?yàn)樵?amp;&后面的命令執(zhí)行結(jié)束時(shí)也會生成一個(gè)返回碼,如果該返回碼不是真值(0代表 true),||后面的命令也會執(zhí)行,例如:
i=0 true && ((i++)) || ((i--)) echo $i # 輸出 0
看起來上面的結(jié)果應(yīng)該是返回1,但是結(jié)果卻是輸出0,為什么呢?原因是這里 i++ 和 i– 都執(zhí)行了一遍。
其中,((i++))命令執(zhí)行算術(shù)運(yùn)算,表達(dá)式計(jì)算的結(jié)果為0。這里和 C 語言一樣,表達(dá)式的結(jié)果為0被認(rèn)為是 false。所以當(dāng) i=0 的時(shí)候,((i++))命令執(zhí)行的返回碼為1(false),從而會執(zhí)行接下來的((i–))命令。
如果我們在這里使用前綴自增運(yùn)算符的話,返回的結(jié)果恰恰為1,因?yàn)?(++i))執(zhí)行的返回碼是0(true):
i=0 true && (( ++i )) || (( --i )) echo $i # Prints 1
不過在你無法保證 y 的執(zhí)行結(jié)果是,絕對不要依靠 x && y || z這種寫法。上面這種巧合,在 i 初始化為-1時(shí)也會有問題。
如果你喜歡代碼更加安全健壯,建議使用 if…fi 語法:
i=0 if true; then ((i++)) else ((i--)) fi echo $i # 輸出 1
23. echo “Hello World!”
在交互式的 Shell 環(huán)境下,你執(zhí)行以上命令會遇到下面的錯(cuò)誤:
bash: !": event not found 這是因?yàn)椋谀J(rèn)的交互式 Shell 環(huán)境下,Bash 發(fā)現(xiàn)感嘆號時(shí)會執(zhí)行歷史命令展開。在 Shell 腳本中,這種行為是被禁止的,所以不會發(fā)生錯(cuò)誤。 不幸地是,你認(rèn)為明顯正確地修復(fù)方法,也不能工作,你會發(fā)現(xiàn)反斜杠并沒有轉(zhuǎn)義感嘆號: # echo "hi!" hi! 最簡單地方法是禁用 histexpand 選項(xiàng),你可以通過 set +H 或者 set +o histexpand 命令來完成。 下面四種寫法都可以解決: # 1. 使用單引號 echo 'Hello World!' # 2. 禁用 histexpand 選項(xiàng) set +H echo "Hello World!" # 3. 重置 histchars histchars= # 4. 控制 shell 展開的順序,命令行歷史展開是在單詞拆分之前執(zhí)行的 # 參見:Bash man 手冊的History Expansion一節(jié) exmark='!' echo "Hello, world$exmark"
由于篇幅限制,本系列文章會分成多篇文章,下一篇參見第節(jié):Bash編程易犯的錯(cuò)誤。