亚洲国产第一_开心网五月色综合亚洲_日本一级特黄特色大片免费观看_久久久久久久久久免观看

Hello! 歡迎來到小浪云!


第三節:Bash編程易犯的錯誤


avatar
小浪云 2024-12-15 179

第三節:Bash編程易犯的錯誤

上一篇文章參見 第二節:bash編程易犯的錯誤。

24. for arg in $*

和大多數 Shell 一樣,Bash 支持依次讀取單個命令行參數的語法。不過這并是$*或者$@,這兩種寫法都不正確,它們只能得到完整的參數列表,并非單獨的一個個參數。

正確的語法是(沒錯要加上引號):

 for arg in "$@" 

# 或者更簡單的寫法

 for arg 

在腳本中遍歷所有參數是一個再普遍不過的需求,所以 for arg 默認等價于 for arg in “$@”。$@使用雙引號后就有特殊的魔力,每個參數展開后成為一個獨立的單詞。(”$@”等價于”$1” “$2” “$3” …)

下面是一個錯誤的例子:

 for x in $*; do    echo "parameter: '$x'" done  執行的結果為:  $ ./myscript 'arg 1' arg2 arg3 parameter: 'arg' parameter: '1' parameter: 'arg2' parameter: 'arg3' 

正確的寫法:

 for x in "$@"; do    echo "parameter: '$x'" done  執行的結果為:  $ ./myscript 'arg 1' arg2 arg3 parameter: 'arg 1' parameter: 'arg2' parameter: 'arg3' 

上面正確的例子中,第一個參數’arg 1’在展開后依然是一個獨立的單詞,而不會被拆分成兩個。

25. function foo()

這種寫法不一定能夠兼容所有 shell,兼容的寫法是:

 foo() {   ... } 

26. echo “~”

波浪號展開(Tilde expansion)僅當~沒有引號的時候發生,在上面的例子中,只會向標準輸出打印~符號,而不是當前用戶的家目錄路徑。

當用引號將路徑參數引起來時,如果要用引號將相對于家目錄的路徑引起來時,推薦使用 $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)

當在函數中聲明局部變量時,local作為一個獨立的命令,這種奇特的行為有時候可能會導致困擾。比如,當你想要捕獲命令替換的返回碼時,你就不能這樣做。local命令的返回碼會覆蓋它。

這種情況下,你只能分成兩行寫:

 local varname varname=$(command) rc=$? 

28. export foo=~/bar

export 與 local 命令一樣,并不是賦值語句的一部分。因此,在有些 Shell 下(比如Bash),export foo=~/bar會展開,但是有些(比如 dash)卻不行。

下面是兩種比較健壯的寫法:

 foo=~/bar; export foo    # Right! export foo="$HOME/bar"   # Right! 

29. sed ‘s/$foo/good bye/’

單引號內部不會展開 $foo變量,在這里可以換成雙引號:

 foo="hello"; sed "s/$foo/good bye/" 

但是要注意,如果你使用了雙引號,就需要考慮更多轉義的事情,具體可以看Quotes這一頁。.

30. tr [A-Z] [a-z]

這里至少有三個問題。第一個問題是, [A-Z] 和 [a-z] 會被 shell 認為是通配符。如果在當前目錄下沒用文件名為單個字母的文件,這個命令似乎能正確執行,否則會錯誤地執行,也許你會在周末耗費許多小時來修復這個問題。

第二個問題是,這不是 tr 命令正確的寫法,實際上,上面的命令會把[轉換成[,將任意大寫字符轉換成對應的小寫字符,將]轉換成],所以你根本不需要加上括號,這樣第一個問題就可以解決了。

第三個問題是,上面的命令執行結果依賴于當前的 locale,A-Z 或者 a-z 不一定會代表26個 ASCII 字母。實際上,在一些語言環境下,z 位于字母表的中間位置。這個問題的解法,取決于你希望發生的行為是哪一種。

如果你僅希望改變26個英文字母的大小寫(強制 locale為 C):

 LC_COLLATE=C tr A-Z a-z  如果你希望根據實際的語言環境來轉換:  tr '[:upper:]' '[:lower:]' 

31. ps ax | grep gedit

這里的根本問題是正在運行的進程名稱,本質上是不可靠的。可能會有多個合法的gedit進程,也有可能是別的東西偽裝成gedit進程(改變執行命令名稱是一件簡單的事情 ),更多細節可以看ProcessManagement這一篇文章。

執行以上命令,往往會在結果中包含 grep 進程:

 # ps ax | grep gedit 10530 ?        S      6:23 gedit 32118 pts/0    R+     0:00 grep gedit  這個時候,需要過濾多余的結果:  # ps ax | grep -v grep | grep gedit  上面的寫法比較丑陋,另外一種方法是:  # ps ax | grep [g]edit 

32. printf “$foo”

如果$foo 變量的值中包括或者%符號,上面命令的執行結果可能會出乎你的意料之外。

下面是正確的寫法:

 printf %s "$foo" printf '%s ' "$foo" 

33. for i in {1..$n}

Bash的命令解釋器會優先展開大括號,所以這時大括號{}表達式里面看到的是文字上的$n(沒有展開)。$n 不是一個數值,所以這里的大括號{}并不會展開成數字列表。可見,這導致很難使用大括號來展開大小只能在運行時才知道的列表。

可以用下面的方法:

 for ((i=1; i< =n; i++)); do ... done 

注:之前我也有寫過一篇文章來介紹這個問題:Shell生成數字序列。

34. if [[ $foo = $bar ]]

在[[內部,當=號右邊的值沒有用引號引起來,bash 會將它當作模式來匹配,而不是一個簡單的字符串。所以,在上面的例子中 ,如果 bar 的值是一個*號,執行的結果永遠是 true。

所以,如果你想檢查兩側的字符串是否相同,等號右側的值一定要用引號引起來。

 if [[ $foo = "$bar" ]] 

如果你確實要執行模式匹配,聰明的做法是取一個更加有意義的變量名(例如$patt),或者加上注釋說明。

35. if [[ $foo =~ ‘some RE’ ]]

同上,如果=~號右側的值加上引號,它會散失特殊的正則表達式含義,而變成一個普通的字符串

如果你想使用一個長的或者復雜的正則表達式,避免大量的反斜杠轉義,建議把它放在一個變量中:

 re='some RE' if [[ $foo =~ $re ]] 

由于篇幅限制,本系列文章會分成多篇文章,最后一篇參見 第四節:Bash編程易犯的錯誤。

相關閱讀