Shell 腳本的持續整合實踐 文章首圖

Shell 腳本的持續整合實踐

Shell 腳本的持續整合實踐

在現代軟體開發流程中,持續整合(Continuous Integration, CI)已成為確保程式碼品質與部署穩定性的關鍵環節。雖然 Jenkins、GitLab CI 或 GitHub Actions 等工具提供了強大的圖形化介面,但許多開發者往往忽略了 Shell 腳本本身在 CI 流程中的基礎地位。一個撰寫精良的 Shell 腳本,不僅能作為 CI/CD 流水線的核心執行單元,更能幫助我們深入理解自動化部署的本質。本文將探討如何撰寫具備韌性(Robustness)的 Shell 腳本,並提供可直接在 Ubuntu 22.04 或 Debian 12 上執行的實戰範例。

撰寫高品質 Shell 腳本的關鍵原則

在將 Shell 腳本納入 CI 流程之前,我們必須先確立幾個核心原則。首先,錯誤處理機制不可或缺。默認情況下,Shell 腳本即使遇到錯誤也會繼續執行,這在自動化流程中是極其危險的。其次,環境變數的管理應保持乾淨,避免污染系統全域環境。最後,腳本的輸出應具備可讀性,便於日誌分析與除錯。

1. 嚴格的錯誤處理

使用 set -euo pipefail 是撰寫生產環境腳本的黃金法則:

  • -e:若任何指令回傳非零狀態碼(即失敗),立即終止腳本。
  • -u:將未定義的變數視為錯誤,防止因拼寫錯誤導致的潛在問題。
  • -o pipefail:確保管線(pipe)中任何一個指令失敗,整個管線即視為失敗。

2. 結構化與可維護性

將重複使用的邏輯封裝為函式,並使用明確的註解。避免在腳本中硬編碼路徑或密碼,應透過環境變數或配置檔注入。

實戰範例:自動化部署腳本

以下是一個模擬前端專案構建與部署的 Shell 腳本。此腳本假設我們已經安裝了 Node.js,並模擬從版本控制系統拉取程式碼、安裝依賴、執行測試到複製檔案的完整過程。

請將以下內容儲存為 deploy.sh,並賦予執行權限 chmod +x deploy.sh

#!/usr/bin/env bash
#
# 腳本名稱: deploy.sh
# 描述: 模擬 CI 流程中的構建與部署步驟
# 環境需求: Ubuntu 22.04 / Debian 12, Bash 5+
#

# 啟用嚴格模式
set -euo pipefail

# --- 配置區 ---
readonly PROJECT_NAME="my-app"
readonly BUILD_DIR="./build"
readonly DEPLOY_TARGET="/tmp/deploy-target"
readonly LOG_FILE="./ci.log"

# --- 函式定義 ---

# 記錄日誌函式,同時輸出到螢幕與檔案
log() {
    local message="$1"
    local timestamp
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] $message" | tee -a "$LOG_FILE"
}

# 清理舊資料
cleanup() {
    log "正在清理舊的構建資料..."
    rm -rf "$BUILD_DIR"
    log "清理完成。"
}

# 模擬構建過程
build() {
    log "開始構建專案: $PROJECT_NAME"

    # 建立構建目錄
    mkdir -p "$BUILD_DIR"

    # 模擬安裝依賴 (在此範例中僅為顯示訊息)
    log "模擬執行 npm install..."
    sleep 1 # 模擬耗時操作

    # 模擬執行測試
    log "模擬執行測試套件..."
    # 這裡可以替換為實際的測試指令,如 npm test
    if ! true; then
        log "錯誤:測試失敗!"
        exit 1
    fi
    log "測試通過。"

    # 模擬編譯
    log "模擬編譯程式碼..."
    echo "<html><body>Hello CI</body></html>" > "$BUILD_DIR/index.html"
    log "構建成功,輸出目錄: $BUILD_DIR"
}

# 模擬部署過程
deploy() {
    log "開始部署至目標目錄: $DEPLOY_TARGET"

    # 確保目標目錄存在
    mkdir -p "$DEPLOY_TARGET"

    # 複製檔案
    cp -r "$BUILD_DIR"/* "$DEPLOY_TARGET"/

    # 驗證部署結果
    if [[ -f "$DEPLOY_TARGET/index.html" ]]; then
        log "部署成功!檔案已驗證。"
    else
        log "錯誤:部署驗證失敗,檔案未正確複製。"
        exit 1
    fi
}

# --- 主程式流程 ---

main() {
    log "========================================="
    log "啟動 CI 部署流程"
    log "========================================="

    # 1. 清理舊資料
    cleanup

    # 2. 構建專案
    build

    # 3. 部署
    deploy

    log "========================================="
    log "CI 流程執行完畢,狀態:成功"
    log "========================================="
}

# 執行主程式
main "$@"

常見問題與解決方案

1. 腳本在 CI 環境中執行失敗,但本地運行正常

這通常是由於環境變數差異或權限問題。在 CI 環境中,使用者通常不是 root,且環境變數較少。

  • 解決方案:確保腳本使用絕對路徑或相對路徑正確解析依賴。使用 #!/usr/bin/env bash 而非 #!/bin/bash 可提高相容性。此外,務必檢查 CI 平台是否正確傳遞了環境變數(如 API Keys 或路徑)。

2. 如何處理敏感資訊(如密碼、金鑰)?

絕對不要將密碼硬編碼在腳本中。

  • 解決方案:使用 CI 平台提供的加密環境變數功能(如 GitHub Secrets 或 GitLab CI Variables)。在腳本中透過 $VARIABLE_NAME 引用,並在執行前檢查其是否存在:
    if [[ -z "${DEPLOY_KEY+x}" ]]; then
        log "錯誤:未設定 DEPLOY_KEY 環境變數"
        exit 1
    fi

小結

Shell 腳本是 CI/CD 流水線的基石。透過實踐 set -euo pipefail、結構化程式碼以及妥善管理環境變數,我們可以打造出既穩定又易於維護的自動化流程。雖然現代 CI 工具提供了豐富的整合功能,但理解底層的 Shell 邏輯,能讓開發者在遇到複雜情境時,擁有更強大的除錯與優化能力。建議讀者將本文的範例複製到本地環境,並嘗試加入更多實際的構建步驟,以加深對持續整合實踐的理解。