前言
最近在公司使用Jenkins自动化编译前端、Android、iOS时遇到了挺多的shell脚本的坑,以前都是从网上找一些脚本改改测试可用就直接用了,但是最近项目变化大,导致自动化编译总是出错,于是决定好好学习下shell脚本如何正确的编写!以下是我个人的实际项目所用的一些总结,我大致会围绕三个问题来聊聊我遇到的坑和解决方法:
- 如何根据git的提交记录,判断代码的变化,决定是否需要编译!
- 检测命令是否存在,命令不存在时如何捕获错误?
- 判断一条命令的执行结果,根据结果判断是否需要特殊处理
由于本人只是一个前端开发者,对于Linux的shell脚本还处于一脸懵逼状态,可能我的方法和您的不一致,如果您感觉我的方法和思路有什么不对的地方还请大神给予指正~
1. 如何根据git的提交记录,判断代码的变化,决定是否需要编译!
首先将可用代码贴出来:
#获取上次提交和本次提交的差异
git diff --name-only $GIT_PREVIOUS_SUCCESSFUL_COMMIT $GIT_COMMIT > ${WORKSPACE}/${APP_ID}commint.log
rowNum=$(awk 'END{print NR}' "${WORKSPACE}/${APP_ID}commint.log")
# 文件更新数 小于等于 0 无需构建
if [ $rowNum -le 0 ];then
echo "代码没有任何修改,项目无需构建"
exit 0
fi
1.1 从第一句话开始
git diff --name-only $GIT_PREVIOUS_SUCCESSFUL_COMMIT $GIT_COMMIT > ${WORKSPACE}/${APP_ID}commint.log
这句话实现了两个功能:
- 得到上次git提交和本次提交的差异文件
git diff --name-only $GIT_PREVIOUS_SUCCESSFUL_COMMIT $GIT_COMMIT
- 将得到的差异输出到一个文件中
> ${WORKSPACE}/${APP_ID}commint.log
查看git两次提交的差异
我们知道git diff 可以得到文件的差异,但是这里我们只需要得到差异的文件名称就可以,根据git官网文档得到 --name-only
参数可得到文件名称,那么 $GIT_PREVIOUS_SUCCESSFUL_COMMIT 和 $GIT_COMMIT
是什么呢?
根据Jenkins官方文档中的可用的shell变量一文得知
- $GIT_PREVIOUS_SUCCESSFUL_COMMIT :代表上次git提交的commit ID
- $GIT_COMMIT :代表本次git提交的 commit ID
- ${WORKSPACE} : Jenkins的工作空间绝对地址
- ${APP_ID} : 当前Jenkins的任务名称
OK,得到两次提交的差异文件列表之后为方便后续使用,我们可以利用shell 中的 >
重定向功能把结果输出到一个文件中!
参考shell脚本编程文档得知:
>
将前一个命令的标准输出结果 以文本替换的方式重写
进一个文件中,当文件不存在时自动创建该文件>>
将前一个命令的标准输出以追加
的形式,追加进一个文件的末尾行,当文件不存在时自动创建该文件
最终利用 git diff --name-only $GIT_PREVIOUS_SUCCESSFUL_COMMIT $GIT_COMMIT > ${WORKSPACE}/${APP_ID}commint.log
句话我们得到了git上次提交跟本次提交的差异文件,并将结果输出到了当前Jenkins任务的工作空间根目录,文件名为commit.log里!
1.2 第二句 rowNum=$(awk 'END{print NR}' "${WORKSPACE}/${APP_ID}commint.log")
我们已经得到了差异的文件信息并写入到了commit.log文件里,那么只需要读取这个文件,统计下这个文件里有多少行,是不是就可以得到本次修改了多少个文件?
根据《Linux+shell脚本攻略(第二版)》一书第四章 让文本飞 一文中得知 使用awk 可以统计文件的行数!
awk简介
awk特殊变量
使用awk统计文件中的行数
所以我们得到了这条统计文件内容行数的命令,并将改命令的结果赋予rowNum
变量
$ rowNum=$(awk 'END{print NR}' "${WORKSPACE}/${APP_ID}commint.log")
1.3 接下来的文件内容数量的判断
根据《Linux命令行于shell编程大全(第三版)》中 第12章 使用结构化命令 一文中得知:
使用结构化语句
数值比较
if [ $rowNum -le 0 ];then
echo "代码没有任何修改,项目无需构建"
exit 0
fi
2. 检测命令是否存在,命令不存在时如何捕获错误?
在使用shell脚本自动化编译的时候经常会遇到当某个命令不存在或者没有安装的时候直接报错,终止了编译!现在解决的就是当遇到命令找不到的时候直接安装该命令
先把最终实现代码贴出来然后一点点去分析(拿移动端代码热更新举个例子):
#检测有没有code-push-cli,没有直接全局安装
if hash code-push 2>/dev/null; then
echo "有code-push-cli"
else
npm install code-push-cli@latest -g
fi
参考自stackoverflow 方法大致有三种:
command -v <the_command>
hash <the_command> # For regular commands. Or...
type <the_command> # To check built-ins and keywords
- command :shell内建函数,-v 参数主要用于检测命令是否存在,命令构建的退出状态是命令的退出状态
- type : shell内建函数,该命令用来显示指定命令的类型,判断给出的指令为“外部指令”、“命令别名”或者“内部指令”
- hash : shell内建函数,hash命令的作用是在环境变量PATH中搜索命令name的完整路径并记住它,这样以后再次执行相同的命令时,就不必搜索其完整路径了,成功执行时,hash命令的退出状态为0
以上三种方式随意一个就可以了,看个人爱好了!现在来看下 2>/dev/null
的作用,在开始之前首先需要简单说下什么是文件操作符
2.1 文件描述符
当执行shell命令时,每个 Unix/Linux 命令运行时会默认打开3个文件,每个文件有对应的文件描述符来方便我们使用:
类型 | 文件描述符 | 默认情况 | 对应文件句柄位置 |
---|---|---|---|
标准输入(standard input) | 0 | 从键盘获得输入 | /proc/self/fd/0 |
标准输出(standard output | 1 | 输出到屏幕(即控制台) | /proc/self/fd/1 |
错误输出(error output) | 2 | 输出到屏幕(即控制台) | /proc/self/fd/2 |
所以我们平时在执行shell命令中,都默认是从键盘获得输入,并且将结果输出到控制台上。但是我们可以通过更改文件描述符默认的指向,从而实现输入输出的重定向。比如我们将1指向文件,那么标准的输出就会输出到文件中。
2.2 输出重定向
输出重定向的使用方式很简单,基本的一些命令如下:
命令 | 介绍 |
---|---|
command >filename | 把标准输出重定向到新文件中 |
command 1>filename | 同上 |
command >>filename | 把标准输出追加到文件中 |
command 1>>filename | 同上 |
command 2>filename | 把标准错误重定向到新文件中 |
command 2>>filename | 把标准错误追加到新文件中 |
2.3 >/dev/null
/dev/null代表linux的空设备文件,所有往这个文件里面写入的内容都会丢失,俗称“黑洞”。那么执行了>/dev/null之后,标准输出就会不再存在,没有任何地方能够找到输出的内容。
2.4 2>&1
这条命令用到了重定向绑定,采用&可以将两个输出绑定在一起。这条命令的作用是错误输出将和标准输出同用一个文件描述符,说人话就是错误输出将会和标准输出输出到同一个地方。
2.5 >/dev/null 2>&1
linux在执行shell命令之前,就会确定好所有的输入输出位置,并且从左到右依次执行重定向的命令,所以>/dev/null 2>&1的作用就是让标准输出重定向到/dev/null中(丢弃标准输出),然后错误输出由于重用了标准输出的描述符,所以错误输出也被定向到了/dev/null中,错误输出同样也被丢弃了。
参考自详解shell中>/dev/null 2>&1到底是什么
小结
hash code-push 2>/dev/null
用hash
来判断code-push
命令是否存在,并且把标准错误重定向到Linux黑洞中,以保证脚本的完整运行
3. 判断一条命令的执行结果,根据结果判断是否需要特殊处理
拿移动端代码热更新 code-push
举个例子,根据code-push whoami
的执行结果判断当前code-push是否登录,未登录执行登录操作
先贴下代码:
#登录code-push私服
WHOAMI=`code-push whoami 2>&1`
if echo "$WHOAMI" | grep "userName" >/dev/null; then
echo "code-push login Successfully"
else
code-push login
fi
经过上面对文件操作符和重定向的学习,在看如上这段代码是不是好理解多了?
3.1 第一句 WHOAMI=`code-push whoami 2>&1`
正常来说 code-push whoami
输出的是您当前登录的用户名,如果当前处于未登录状态,那么该命令会使用文件操作符2 输出标准的错误信息,那么我们用到 2>&1 重定向绑定语句将标准错误输出和标准输出信息保存在WHOAMI变量中
3.2 第二句关键代码 echo "$WHOAMI" | grep "userName" >/dev/null
该语句的关键在于 Linux 的管道操作符 和 grep 过滤器
- | : Linux 中的管道操作符,用于将操作符左侧的标准输出信息,以标准输入的形式交给操作符右侧的命令!(自己的理解,无参考)
- grep : Linux 中用于找到模式匹配的行,找到了返回状态码为0,未找到返回状态码未2
>/dev/null
将左侧所有的输出重定向到Linux黑洞
总结
我个人的思路就是捕获一切可能返回状态码为2的错误输出!保证程序能够按照预想的正常运行!欢迎拍砖~