欢迎关注 「Android茶话会」
- 回 「学习之路」 取Android技术路线经典电子书
- 回 「pdf」 取阿里&字节经典面试题、Android、算法、Java等系列武功秘籍。
- 回 「天涯」 取天涯论坛200+精彩博文,包括小说、玄学等
背景
之前在搞一些CI/CD,使用到了shell脚本,shell的开箱即用确实比较方便,至少无需在宿主上安装运行环境,本篇文章主要解释shell脚本实践过程中一些经验总结。
实践篇
模块化
刚开始看一些之前的shell脚本,一个脚本大几百行,很少有函数的情况,其实shell脚本也可以函数化,按照模块的拆分,这样就会带来良好的可读性和可维护性,通常我们会先定义main函数,将功能分解为一个个子函数
- 模块化之前
- 模块化之后
#!/bin/bash
localvar="fun1"
main() {
func1
func2
}
func1() {
local localvar="funlocal"
echo ${localvar}
localvar="fun2"
}
func2() {
echo ${localvar}
}
main "$@"
函数
函数是模块化的基础,一个函数往往负责一件事件
- 函数名后面的圆括号不加任何参数
- 函数的完整定义必须置于函数的调用之前
函数名 (){
函数体
}
传参
#!/bin/bash
print_something(){
echo "hello $1" # $1 获取第一个参数
}
print_something Lion # Lion 为参数
print_something Frank # Frank 为参数
$1~$9:函数的第一个到第9个的参数。
$0:函数所在的脚本名。
$#:函数的参数总数。
$@:函数的全部参数,参数之间使用空格分隔。
$*:函数的全部参数,参数之间使用变量$IFS值的第一个字符分隔,默认为空格,但是可以自定义。
$?:显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。可以用于函数返回值
返回值
testFun(){
echo "helloworld!"
return 99
}
# 千万要注意shell并不像其他语言直接返回返回值,其返回值放到$?中,这也是为什么只能返回整型的原因
# 所以这种承接方法是错误的,获取到的值是echo打印的内容
# return_value=`testFun`
# 以下才是正确获取通过return返回的返回值的正确写法
testFun
echo "the return value is: $?"
局部变量
- 不做特殊声明,shell中变量都是全局变量
- 局部变量 使用 local 关键字,函数内外同时存在同名变量,则函数内部会覆盖函数外部变量
脚本之间引用
模块化之后多个脚本和公共参数之间是可以相互复用的 这时候可以通过 souce或者点号来调用所需要的脚本
source ./util.sh
. ./util.sh
错误处理
如果什么都不做,在shell中命令出错也不影响,默认会继续执行,这会带来麻烦,有时候我们需要区分业务错误和系统错误,比如在脚本执行遇到系统错误之后就应该退出,遇到业务错误,需要根据业务错误来确定是否往下执行,有以下几种方式来控制shell的错误
set 命令
- set -e
只要脚本发生错误就终止执行,set +e表示关闭-e选项,set -e表示重新打开-e选项,但是要注意这个命令不适与管道操作
set +e
command1
command2
set -e
管道处理需要借助
- set -o pipeline
通常我们会把这些命令放在一起使用
# 写法一
set -Eeuxo pipefail
# 写法二
set -Eeux
set -o pipefail
短路符号
如果command正常退出,返回0,|| 运算符右半部分被短路,脚本继续执行。
如果command异常退出,返回非0, 运算符右半部分执行,脚本exit 1。
command || exit 1
# 写法一
command || { echo "command failed"; exit 1; }
# 写法二
if ! command; then echo "command failed"; exit 1; fi
# 写法三
command
if [ "$?" -ne 0 ]; then echo "command failed"; exit 1; fi
使用trap 捕获信号量
用来在bash脚本中响应系统信号,trap命令必须放在脚本的开头。否则,它上方的任何命令导致脚本退出,都不会被它捕获。标准格式
$ trap [动作] [信号1] [信号2] ...
HUP:编号1,脚本与所在的终端脱离联系。
INT:编号2,用户按下 Ctrl + C,意图让脚本终止运行。
QUIT:编号3,用户按下 Ctrl + 斜杠,意图退出脚本。
KILL:编号9,该信号用于杀死进程。
TERM:编号15,这是kill命令发出的默认信号。
EXIT:编号0,这不是系统信号,而是 Bash 脚本特有的信号,不管什么情况,只要退出脚本就会产生。
$ trap 'rm -f "$TMPFILE"' EXIT
表示 脚本遇到EXIT信号时,就会执行rm -f “$TMPFILE”
调试
也没有特别好的办法,可以让不同级别的日志打印出不同的颜色
function debug()
{
echo -e "\033[37m$1\033[0m"
}
function infolog()
{
echo -e "\033[32m$1\033[0m"
}
function warn()
{
echo -e "\033[33m$1\033[0m"
}
function error()
{
echo -e "\033[31m$1\033[0m"
}
其他细节
预定义默认值
- ${varname:-word} varname存在且不为空,则返回它的值,否则返回word
- ${varname:=word} varname存在且不为空,则返回它的值,否则将它设置为word并返回word
- ${varname:+word} varname存在且不为空,在返回word,否则返回空值,它的目的是测试变量是否存在
- ${varname:?message} 如果变量varname存在且不为空,则返回它的值,否则打印出varname: message,并中断脚本的执行,它的目的是防止变量未定义
( ) 与 ()与 ()与{}的区别
前者用于命令执行,返回命令返回值
all_files=`ls` # 获取ls命令的执行结果
all_files=$(ls) # 效果同上
后者用于变量展开
echo ${A}B
[]和[[ ]] 、(())
在使用[]或者test指令进行字符串判空时,需要在引用的变量上加上双引号""。
如果使用[[]]的话就不需要。
$(())用来做整数运算的
curl中携带参数
curl中需要用单引号,数字和字符还不一样,注意tesMsg
jobId="78707463"
tesMsg="msg:需要找专人审批"
curl -X POST https://xxxx/openapi/xxxx/job/update_msg -H "Content-Type: application/json" -d '{
"jobId":'$jobId',
"jobMsg":"'"${tesMsg}"'"
}'
- 回 「学习之路」 取Android技术路线经典电子书
- 回 「pdf」 取阿里&字节经典面试题、Android、算法、Java等系列武功秘籍。
- 回 「天涯」 取天涯论坛200+精彩博文,包括小说、玄学等
您的 点赞、评论、转发 是对我的巨大鼓励!