Bash 腳本的 10 個最佳實踐 文章首圖

Bash 腳本的 10 個最佳實踐

Bash 腳本的 10 個最佳實踐

在 Linux 系統管理與自動化運維中,Bash 腳本是最基礎也最強大的工具之一。然而,許多初學者寫出的腳本往往充滿了隱含的 Bug 或難以維護的結構。為了提升腳本的穩定性、可讀性與安全性,本文將整理 10 個實用的 Bash 腳本最佳實踐,並提供完整的程式碼範例,幫助你寫出更專業的 Shell 腳本。

1. 使用嚴謹模式 (Strict Mode)

撰寫 Bash 腳本時,第一行應加入 set -euo pipefail。這三個參數分別代表:

  • -e:若任何指令回傳非零狀態碼(即發生錯誤),腳本立即中止。
  • -u:使用未設定的變數時,視為錯誤。
  • -o pipefail:管線指令中,若任一指令失敗,整個管線視為失敗。

這能避免「靜默失敗」的情況,讓除錯變得更容易。

2. 明確指定 Shell 與使用絕對路徑

確保腳本在正確的環境中執行。在 shebang 中指定 /usr/bin/env bash 比直接寫 /bin/bash 更具可移植性。此外,呼叫外部指令時,盡量使用絕對路徑,避免受到 $PATH 環境變數污染的影響。

3. 妥善處理變數展開

當變數內容包含空格或特殊字元時,務必使用雙引號將變數包起來。例如 "$VAR" 而非 $VAR。這能防止 Word Splitting 帶來的意外行為。

4. 使用 [[ ]] 而非 [ ]

在 Bash 中,建議使用雙括號 [[ ]] 進行條件判斷,因為它支援正規表示式匹配(=~)且對空格的要求較寬鬆,相對 [ ] 更安全且功能更強大。

5. 避免在管線中使用子殼層

在管線中,右側的指令通常在子殼層(subshell)中執行,這意味著在子殼層中設定的變數不會影響父殼層。若需共享變數,應考慮使用變數替代或文件描述符。

6. 檢查依賴性

在腳本執行關鍵操作前,應檢查必要的外部指令或檔案是否存在。可使用 command -v 檢查指令是否存在,或使用 -e 檢查檔案是否存在。

7. 使用函數封裝邏輯

將重複或獨立的邏輯封裝成函數,不僅能提高可讀性,也方便單元測試與除錯。函數名稱應清晰描述其功能,並避免與系統指令衝突。

8. 記錄日誌而非僅使用 echo

在生產環境中,應使用日誌記錄系統(如 logger 或自訂日誌函數)來輸出訊息,而非單純使用 echo。這有助於將輸出導向 syslog 或特定日誌檔,便於後續追蹤。

9. 處理臨時檔案

若腳本需要創建臨時檔案,應使用 mktemp 指令,並確保在腳本結束時清理這些檔案。這能避免權限衝突與磁碟空間洩漏。

10. 註解與文件化

雖然程式碼應力求自解釋,但複雜的業務邏輯或特殊考量仍需註解說明。此外,提供簡短的 Usage 說明有助於其他使用者快速上手。


實戰範例:一個符合最佳實踐的備份腳本

以下是一個整合了上述最佳實踐的完整 Bash 腳本範例。此腳本會檢查依賴項、創建臨時目錄、執行備份並記錄日誌。

#!/usr/bin/env bash
# 啟用嚴謹模式
set -euo pipefail

# 定義變數
BACKUP_DIR="/tmp/backup_$$"
SOURCE_DIR="/etc/nginx"
LOG_FILE="/var/log/backup_script.log"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")

# 定義日誌函數
log() {
    local level="$1"
    shift
    local message="$*"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message" | tee -a "$LOG_FILE"
}

# 清理函數
cleanup() {
    if [[ -d "$BACKUP_DIR" ]]; then
        rm -rf "$BACKUP_DIR"
        log "INFO" "臨時目錄已清理"
    fi
}

# 註冊清理函數,確保腳本退出時執行
trap cleanup EXIT

# 主執行邏輯
main() {
    log "INFO" "開始執行備份任務"

    # 檢查依賴指令
    if ! command -v tar &> /dev/null; then
        log "ERROR" "找不到 tar 指令,請確認已安裝 coreutils"
        exit 1
    fi

    # 檢查來源目錄是否存在
    if [[ ! -d "$SOURCE_DIR" ]]; then
        log "ERROR" "來源目錄 $SOURCE_DIR 不存在"
        exit 1
    fi

    # 創建臨時目錄
    BACKUP_DIR=$(mktemp -d)
    log "INFO" "使用臨時目錄: $BACKUP_DIR"

    # 執行備份
    local backup_file="${BACKUP_DIR}/nginx_backup_${TIMESTAMP}.tar.gz"
    if tar -czf "$backup_file" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"; then
        log "INFO" "備份成功: $backup_file"
        # 這裡可以加入後續的上傳或移動邏輯
    else
        log "ERROR" "備份失敗"
        exit 1
    fi
}

# 執行主函數
main "$@"

常見問題

Q1: 為什麼 set -e 有時會導致腳本意外中止? A: 因為 set -e 會對任何非零回傳值敏感。例如,在 if 條件判斷中使用的指令,即使失敗也不會觸發中止,因為其回傳值已被 if 語法吸收。但若在 if 之外的普通命令列上發生錯誤,腳本就會立即退出。

Q2: 如何除錯腳本中的變數展開問題? A: 可以使用 set -x 開啟追蹤模式,腳本會在執行前打印出每個指令及其展開後的實際內容。這能幫助你發現變數是否因空格或特殊字元而被錯誤拆分。除錯完成後,請記得移除 set -x 以避免影響生產環境效能。


小結

撰寫高品質的 Bash 腳本不僅是讓指令跑起來而已,更需考慮錯誤處理、可讀性與安全性。透過採用嚴謹模式、妥善處理變數、使用函數封裝以及記錄日誌等最佳實踐,你能顯著提升腳本的穩定性與維護性。希望本文的 10 個技巧與範例能成為你日常 Linux 自動化工作的得力助手。