MonkeyBinBin

被程式設計的猴子

Shell script

利用 shell script 快速產出前端專案交付上版檔案


近期專案進入測試階段,經常需要包版與加密壓縮交付給客戶,每次手動都要花費不少時間(又怕出錯)。為了可以準時下班,就想說來寫一個 shell script 來做這段重覆又花時間的事情。咻一下就可以把檔案準備好了,快又爽!

當專案開發進入測試階段之後,每天都會上演的戲碼就是包版做程式的更新。為了每天可以準時下班,就必需讓包版自動化。一方面可以省時,也可以減少犯錯的可能。接下來,我使用 React 專案來做範例,此專案已設定好不同環境(uat、prod)的 scripts 透過 npm、webpack 打包程式,說明在 Mac OS 如何使用 shell script 來進行打包、壓縮加密、更新版本,產出應交付的檔案。

需求說明

  1. 上版環境區分為 uat 與 prod。
  2. 上版檔案需加密壓縮,密碼為"test123"。
  3. 檔案名稱分別為 "xxx v0.0.0 uat.zip" 、 "xxx v0.0.0 prod.zip",其中0.0.0為版號。
  4. 產出交付 zip 檔後,版控也需要更新至最新版號。

使用到的 shell script 指令

  1. read
  2. echo
  3. date
  4. cd
  5. sed
  6. for...in
  7. if
  8. rm
  9. mkdir
  10. cp
  11. expect、spawn、send

如何執行 .sh 檔案?

  1. 開啟「終端機」,將資料夾移至 .sh 檔案所在的資料夾。
  2. 直接輸入"sh filename.sh"。

1. 建立 .sh 檔案。

使用文字編輯器,增加以下內容,定義所要使用的 shell。

#!/bin/bash

2. 設定變數與要求使用者輸入資料。

  • 所有變數都視為字串。
  • 定義變數時,等號二邊不可以有空白。
  • date 為取得目前日期指令,後方 +"%Y%m%d%H%M" 用來格式化日期並輸出字串。
  • 使用 $(date +"%Y%m%d%H%M") 或 `date +"%Y%m%d%H%M"` 取得執行後的結果字串(執行結果輸出範例:201908151235)。
  • 使用變數時在前方加上 $ 符號(例:$TARGET_PATH)。
  • 使用 read 指令來讀取使用者輸入的資料。
PROJECT_PATH='/Users/demo/Projects/react-app'
TARGET_PATH='/Users/demo/react-app'
BUILD_OUTPUT_FOLDER=$(date +"%Y%m%d%H%M")
BUILD_OUTPUT_PATH=$TARGET_PATH/$BUILD_OUTPUT_FOLDER
ZIP_PASSWORD='test123'

read -p '請輸入版本號:' version

3. 修改 react 專案檔案,更新版本號碼。

  • 先將目錄移至 react 專案目錄,方便修改專案檔案與執行打包指令。
  • 使用 sed 指令修改專案 package.json 檔案內的 version 設定。
  • sed 指令後方的 -i 為直接修改讀取的檔案內容,接續的 "" 用來設定備份檔案名稱的後綴(suffix),如不需備份則設定空字串。
  • sed 指令後方的 's/"version": "[0-9][0-9].[0-9][0-9]*.[0-9][0-9]",/"version": "'"$version"'",/g' 是用來指定動作, s 表示取代,s/被取代的字串/取代的字串/g,被取代的字串可搭配正規表示法。
  • sed 最後一個參數為要被修改的檔案。
cd $PROJECT_PATH
sed -i "" 's/"version": "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*",/"version": "'"$version"'",/g' package.json

4. 建立輸出檔案使用的資料夾。

  • 先判斷檔案輸出的資料夾是否存在,不存在才建立資料夾。
  • 指令中如果有需要空白字串,需在空字串前增加 \ 符號。
  • mkdir 建立資料夾,參數 -p 表示當父層資料夾不存在時一併建立。
if [ ! -d $BUILD_OUTPUT_PATH/xxx\ v$version\ uat ]
then
  mkdir -p $BUILD_OUTPUT_PATH/xxx\ v$version\ uat
fi

5. 執行 npm 指令,產出打包檔案。

npm run build-uat

6. 將打包產出的檔案複製到"步驟4"建立的資料夾,提供稍後加密壓縮使用。

  • 使用 cp 指令複製檔案至指定資料夾。
  • 參數 -r 表示複製資料夾以及裡面全部內容。
  • cp -r [from folder] [to folder]。
cp -r $PROJECT_PATH/build/* $BUILD_OUTPUT_PATH/xxx\ v$version\ uat/

7. 加密壓縮。

  • 先將目錄移至"步驟4"建立的資料夾。
  • 使用 zip 指令進行壓縮,參數 -e 為加密壓縮,參數 -r 為壓縮資料夾以及裡面全部內容。
  • 利用 expect 來處理 zip 過程中需要使用者輸入密碼的動作。
  • spawn、send 為 expect 的指令,所以要在 bash 下執行需使用 expect -c "…" 這種特別的寫法,並且在雙引號內的雙引號前需加上 \。
  • \r 表示按下 enter 送出的意思。
cd $BUILD_OUTPUT_PATH
expect -c "
  spawn zip -er ./xxx\ v$version\ uat.zip ./xxx\ v$version\ uat/
  expect \"Enter password\"
  send \"$ZIP_PASSWORD\r\"
  expect \"Verify password\"
  send \"$ZIP_PASSWORD\r\"
  expect eof
"
cd $PROJECT_PATH

8. 增加 for 迴圈,重覆執行 4~7 的步驟,以符合打包多個環境之需求。

  • 設定環境變數,多個使用空白區隔。
ENV='uat prod'
for value in $ENV; do
  if [ ! -d $BUILD_OUTPUT_PATH/xxx\ v$version\ $value ]
  then
    mkdir -p $BUILD_OUTPUT_PATH/xxx\ v$version\ $value
  fi

  npm run build-$value

  cp -r $PROJECT_PATH/build/* $BUILD_OUTPUT_PATH/xxx\ v$version\ $value/

  cd $BUILD_OUTPUT_PATH
  expect -c "
    spawn zip -er ./xxx\ v$version\ $value.zip ./xxx\ v$version\ $value/
    expect \"Enter password\"
    send \"$ZIP_PASSWORD\r\"
    expect \"Verify password\"
    send \"$ZIP_PASSWORD\r\"
    expect eof
  "
  cd $PROJECT_PATH
done

9. 詢問是否需要更新版控。

  • 設定變數預設值的方式為, xxx=${xxx:-預設值}。
read -p "確定要更新版控的版本號碼至 $version (y/N)?" isUpdateGitVersion
isUpdateGitVersion=${isUpdateGitVersion:-N}
if [ $isUpdateGitVersion = 'Y' ] || [ $isUpdateGitVersion = 'y' ]; then 
  echo "Updating git..."
  git commit -a -m "release v$version"
  git push
  echo "The version control is updated to $version."
else
  echo "The version control is not updated."
fi

完整 .sh 內容

增加一些資訊輸出,幫助了解目前 shell script 執行的狀況

#!/bin/bash
PROJECT_PATH='/Users/demo/Projects/react-app'
TARGET_PATH='/Users/demo/react-app'
BUILD_OUTPUT_FOLDER=$(date +"%Y%m%d%H%M")
BUILD_OUTPUT_PATH=$TARGET_PATH/$BUILD_OUTPUT_FOLDER
ZIP_PASSWORD='test123'

read -p '請輸入版本號:' version

echo "\n### 準備打包版號為:$version ###\n"
echo "專案路徑:$PROJECT_PATH"
echo "檔案輸出路徑:$BUILD_OUTPUT_PATH"
echo "壓縮密碼:$ZIP_PASSWORD"

cd $PROJECT_PATH
sed -i "" 's/"version": "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*",/"version": "'"$version"'",/g' package.json
echo "Project version is updated to $version"

ENV='uat prod'
for value in $ENV; do
  if [ ! -d $BUILD_OUTPUT_PATH/xxx\ v$version\ $value ]
  then
    echo "\nCreating [$BUILD_OUTPUT_PATH/xxx v$version $value] folder"
    mkdir -p $BUILD_OUTPUT_PATH/xxx\ v$version\ $value
    echo "\033[0;37;43m[$BUILD_OUTPUT_PATH/xxx v$version $value] folder created success!\033[0m"
  fi

  npm run build-$value
  echo "\033[0;37;43m$value build success!\033[0m\n"

  echo "Copying file..."
  echo "From => $PROJECT_PATH/build/*"
  echo "To => $BUILD_OUTPUT_PATH/xxx v$version $value/"
  cp -r $PROJECT_PATH/build/* $BUILD_OUTPUT_PATH/xxx\ v$version\ $value/
  echo "\033[0;37;43mCopy file to [$BUILD_OUTPUT_PATH/xxx v$version $value/] success!\033[0m\n"

  cd $BUILD_OUTPUT_PATH
  expect -c "
    spawn zip -er ./xxx\ v$version\ $value.zip ./xxx\ v$version\ $value/
    expect \"Enter password\"
    send \"$ZIP_PASSWORD\r\"
    expect \"Verify password\"
    send \"$ZIP_PASSWORD\r\"
    expect eof
  "
  cd $PROJECT_PATH
done

echo "\n\033[0;37;43mThe package is successful.\033[0m\n"
echo "Output => $BUILD_OUTPUT_PATH\n"

read -p "確定要更新版控的版本號碼至 $version (y/N)?" isUpdateGitVersion
isUpdateGitVersion=${isUpdateGitVersion:-N}
if [ $isUpdateGitVersion = 'Y' ] || [ $isUpdateGitVersion = 'y' ]; then 
  echo "Updating git..."
  git commit -a -m "release v$version"
  git push
  echo "The version control is updated to $version."
else
  echo "The version control is not updated."
fi

其他參考資料

~ END ~