前言
最近在公司使用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的錯誤輸出!保證程序能夠按照預想的正常運行!歡迎拍磚~