shell腳本編碼原則
原則1:shell程式設計中,應該始終對變量進行雙引号引用,包括子shell。不要使用裸露的$符号。
這是因為裸露的$符号可能會導緻變量擴充意外地進行,進而引發一系列問題。
例如,考慮下面這個腳本:
#!/bin/bash
file="my file.txt"
cat $file
在這個腳本中,變量file包含一個檔案名,其中包含空格字元。在執行cat $file指令時,Shell會嘗試将變量file擴充為檔案名,但是由于$file沒有被雙引号引用,Shell會将檔案名中的空格字元解釋為指令參數的分隔符,進而導緻cat指令無法找到檔案,并輸出錯誤資訊。為了解決這個問題,可以對變量file進行雙引号引用,如下所示:
#!/bin/bash
file="my file.txt"
cat "$file"
在這個腳本中,變量file被雙引号引用,進而確定變量擴充時,檔案名中的空格字元不會被解釋為指令參數的分隔符。這樣一來,cat指令就能夠正常地找到檔案并輸出檔案的内容。
類似地,當在子shell中執行指令時,也應該對變量進行雙引号引用。例如,考慮下面這個腳本:
#!/bin/bash
foo=$(echo "bar")
echo $foo
在這個腳本中,$(echo "bar")指令會在一個子shell中執行,并将輸出指派給變量foo。在執行echo $foo指令時,由于$foo沒有被雙引号引用,Shell會将變量foo擴充為其值,進而導緻輸出中包含了換行符。為了避免這個問題,應該對變量進行雙引号引用,如下所示:
#!/bin/bash
foo=$(echo "bar")
echo "$foo"
在這個腳本中,變量foo被雙引号引用,進而確定變量擴充時,輸出中的換行符不會被解釋為多個指令的分隔符。這樣一來,echo指令就能夠正常地輸出變量的值。
綜上所述,始終對變量進行雙引号引用,包括子shell,可以避免變量擴充時産生的意外行為。
原則2:shell程式設計中建議所有代碼都應該放在一個函數中。即使隻有一個函數,也應該将其命名為main
将所有代碼放在一個函數中的好處是可以提高代碼的可維護性和可讀性,同時也可以減少全局變量的使用和命名沖突的可能性。
在Shell中,全局變量的作用域是整個腳本,這意味着它們可以被腳本中的任何函數通路和修改。這種靈活性雖然友善了程式設計,但也增加了代碼的複雜性。如果代碼量很大,全局變量的使用會使代碼難以維護和調試,因為需要考慮全局變量在整個腳本中的影響。
而将所有代碼放在一個函數中,可以将變量的作用域限制在函數内部,避免了全局變量的使用。這樣一來,可以更輕松地調試和修改代碼,因為變量隻在函數内部起作用,不會對整個腳本造成影響。
此外,将所有代碼放在一個函數中,也可以減少命名沖突的可能性。在Shell中,變量和函數都是全局的,如果不小心使用了相同的名稱,就會導緻命名沖突。将所有代碼放在一個函數中,可以将函數的名稱作為字首,進而避免命名沖突。
即使隻有一個函數,也應該将其命名為main,這是因為在Shell中,腳本從頭到尾都是在執行函數。如果沒有将函數命名為main,則可能會造成混淆,不友善其他人閱讀和了解代碼。将函數命名為main可以清晰地表明這是整個腳本的入口點,友善其他人了解代碼。
是以,将所有代碼放在一個函數中,即使隻有一個函數,也應該将其命名為main,可以提高代碼的可維護性和可讀性,同時減少全局變量的使用和命名沖突的可能性。
當代碼量比較大時,将所有代碼放在一個函數中可以提高代碼的可維護性和可讀性。例如,考慮下面這個腳本:
#!/bin/bash
# Global variable
count=0
# Function 1
func1() {
count=$((count + 1))
echo "func1: count = $count"
}
# Function 2
func2() {
count=$((count + 1))
echo "func2: count = $count"
}
# Main program
func1
func2
echo "count = $count"
在這個腳本中,count變量是一個全局變量,可以在func1和func2函數中通路和修改。這個腳本雖然比較簡單,但是如果代碼量更大,全局變量的管理就會變得更加困難。為了避免這個問題,可以将func1和func2函數放在一個主函數main中,如下所示:
#!/bin/bash
# Main program
main() {
# Local variable
local count=0
# Function 1
func1() {
count=$((count + 1))
echo "func1: count = $count"
}
# Function 2
func2() {
count=$((count + 1))
echo "func2: count = $count"
}
# Call functions
func1
func2
echo "count = $count"
}
# Call main function
main
在這個腳本中,func1和func2函數被定義在main函數内部,變量count被定義為局部變量,隻在main函數内部起作用。這樣一來,可以避免全局變量的使用,使代碼更加清晰,易于維護。
另外,即使隻有一個函數,也應該将其命名為main,以表明這是整個腳本的入口點。例如,考慮下面這個腳本:
#!/bin/bash
# Only one function
foo() {
echo "Hello, world!"
}
# Call the function
foo
在這個腳本中,雖然隻有一個函數,但是沒有将其命名為main。這會讓其他人閱讀和了解代碼變得更加困難。為了使代碼更加易于了解,應該将這個函數命名為main,如下所示:
#!/bin/bash
# Main function
main() {
echo "Hello, world!"
}
# Call the function
main
這樣一來,其他人就可以很容易地了解這個腳本是從main函數開始執行的。
總結
shell中編碼存在極大的靈活性,由于shell的版本過多中間存在過很多特性導緻新老shell版本相容存在問題,是以shell程式設計快速落地編寫幾個簡單腳本解決臨時問題很迅速,如果想要編寫很多可維護的shell腳本還是需要深入了解一下各種shell編碼原則和最佳實踐,本篇是原則的開篇,後續還會陸續更新其他編碼原則,敬請期待。另外想要快速了解shell各種編碼生産環境最佳實踐可以關注最近持續更新的《shell腳本編碼最佳實踐》專欄,通過該專欄可以快速了解各種生産環境最佳實踐,避免很多shell的奇葩問題,避免生産環境踩坑。