shell编程实践
背景
最近在搞一些CI/CD,搞了一些脚本,主要是shell,shell的开箱即用确实比较方便,至少无需在宿主上安装运行环境,本篇文章主要解释shell脚本实践过程中一些经验总结。
模块化
刚开始看一些之前的shell脚本,一个脚本大几百行,很少有函数的情况,其实shell脚本也可以函数化,按照模块的拆分,这样就会带来良好的可读性和可维护性,通常我们会先定义main函数,将功能分解为一个个子函数
模块化之前

模块化之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20!/bin/bash
localvar="fun1"
main() {
func1
func2
}
func1() {
local localvar="funlocal"
echo ${localvar}
localvar="fun2"
}
func2() {
echo ${localvar}
}
main "$@"函数
函数是模块化的基础,一个函数往往负责一件事件
函数名后面的圆括号不加任何参数
函数的完整定义必须置于函数的调用之前
1
2
3函数名 (){
函数体
}传参
1
2
3
4
5
6
7!/bin/bash
print_something(){
echo "hello $1" # $1 获取第一个参数
}
print_something Lion # Lion 为参数
print_something Frank # Frank 为参数1
2
3
4
5
61~$9:函数的第一个到第9个的参数。
0:函数所在的脚本名。
#:函数的参数总数。
@:函数的全部参数,参数之间使用空格分隔。
*:函数的全部参数,参数之间使用变量$IFS值的第一个字符分隔,默认为空格,但是可以自定义。
?:显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。可以用于函数返回值返回值
1
2
3
4
5
6
7
8
9
10
11testFun(){
echo "helloworld!"
return 99
}
# 千万要注意shell并不像其他语言直接返回返回值,其返回值放到$?中,这也是为什么只能返回整型的原因
# 所以这种承接方法是错误的,获取到的值是echo打印的内容
# return_value=`testFun`
# 以下才是正确获取通过return返回的返回值的正确写法
testFun
echo "the return value is: $?"局部变量
不做特殊声明,shell中变量都是全局变量
局部变量 使用 local 关键字,函数内外同时存在同名变量,则函数内部会覆盖函数外部变量
脚本之间引用
模块化之后多个脚本和公共参数之间是可以相互复用的 这时候可以通过 souce或者点号来调用所需要的脚本
1 | source ./util.sh |
错误处理
如果什么都不做,在shell中命令出错也不影响,默认会继续执行,这会带来麻烦,有时候我们需要区分业务错误和系统错误,比如在脚本执行遇到系统错误之后就应该退出,遇到业务错误,需要根据业务错误来确定是否往下执行,有以下几种方式来控制shell的错误
set 命令
- set -e
只要脚本发生错误就终止执行,set +e表示关闭-e选项,set -e表示重新打开-e选项,但是要注意这个命令不适与管道操作
1 | set +e |
管道处理需要借助
- set -o pipeline
通常我们会把这些命令放在一起使用
1 | 写法一 |
短路符号
如果command正常退出,返回0,|| 运算符右半部分被短路,脚本继续执行。
如果command异常退出,返回非0, 运算符右半部分执行,脚本exit 1。
1 | command || exit 1 |
使用trap 捕获信号量
用来在bash脚本中响应系统信号,trap命令必须放在脚本的开头。否则,它上方的任何命令导致脚本退出,都不会被它捕获。标准格式
1 | trap [动作] [信号1] [信号2] ... |
HUP:编号1,脚本与所在的终端脱离联系。
INT:编号2,用户按下 Ctrl + C,意图让脚本终止运行。
QUIT:编号3,用户按下 Ctrl + 斜杠,意图退出脚本。
KILL:编号9,该信号用于杀死进程。
TERM:编号15,这是kill命令发出的默认信号。
EXIT:编号0,这不是系统信号,而是 Bash 脚本特有的信号,不管什么情况,只要退出脚本就会产生。
1 | trap 'rm -f "$TMPFILE"' EXIT |
表示 脚本遇到EXIT信号时,就会执行rm -f “$TMPFILE”
调试
也没有特别好的办法,可以让不同级别的日志打印出不同的颜色
1 | function debug() |
其他细节
预定义默认值
${varname:-word} varname存在且不为空,则返回它的值,否则返回word
${varname:=word} varname存在且不为空,则返回它的值,否则将它设置为word并返回word
${varname:+word} varname存在且不为空,在返回word,否则返回空值,它的目的是测试变量是否存在
${varname:?message} 如果变量varname存在且不为空,则返回它的值,否则打印出varname: message,并中断脚本的执行,它的目的是防止变量未定义
$()与${}的区别
前者用于命令执行,返回命令返回值
1 | all_files=`ls` # 获取ls命令的执行结果 |
后者用于变量展开
1 | echo ${A}B |
[]和[[ ]] 、(())
在使用[]或者test指令进行字符串判空时,需要在引用的变量上加上双引号””。
如果使用[[]]的话就不需要。
$(())用来做整数运算的
curl中携带参数
curl中需要用单引号,数字和字符还不一样,注意tesMsg
1 | jobId="78707463" |







