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的奇葩问题,避免生产环境踩坑。