用 Bash 寫健壯的系統監控腳本 文章首圖

用 Bash 寫健壯的系統監控腳本

用 Bash 寫健壯的系統監控腳本

在日常的系統管理工作中,我們經常需要定期檢查伺服器狀態,例如 CPU 使用率、記憶體剩餘空間或磁碟 I/O。雖然 Prometheus 或 Zabbix 等專業監控工具功能強大,但在輕量級場景或快速排錯時,一個簡潔、健壯的 Bash 腳本往往是最高效的解決方案。許多初學者撰寫的監控腳本往往忽略了錯誤處理,導致在異常情況下產生誤報或執行失敗。本文將探討如何撰寫一個具備「健壯性」(Robustness)的 Bash 監控腳本,確保其在各種邊緣情況下依然可靠運作。

為什麼需要健壯的腳本?

所謂的「健壯」,意味著腳本不僅能在正常情況下運行,還能優雅地處理錯誤、驗證輸入並記錄詳細資訊。一個不健壯的腳本可能因為某個命令失敗而導致後續邏輯錯誤,或者因為變數未定義而引發空指針異常(在 Shell 中表現為意外行為)。透過良好的編程習慣,我們可以大幅降低維護成本。

核心技巧與實作範例

我們將撰寫一個名為 sys_monitor.sh 的腳本,它會檢查 CPU 負載、記憶體使用狀況以及根目錄磁碟空間。為了讓腳本更具實用性,我們將加入以下關鍵技術:

  1. 嚴格的錯誤處理:使用 set -euo pipefail 確保腳本在遇到錯誤時立即停止,防止錯誤蔓延。
  2. 函式化設計:將檢查邏輯封裝成函式,提高可讀性與重複使用性。
  3. 輸出格式化:使用顏色與圖示讓日誌更易於閱讀。

以下是完整可執行的腳本程式碼:

#!/bin/bash

# 啟用嚴格的錯誤處理模式
# -e: 若任何命令返回非零值,則立即退出
# -u: 參考未定義變數時視為錯誤
# -o pipefail: 若管線中任何命令失敗,則整個管線視為失敗
set -euo pipefail

# 定義顏色代碼,用於美化輸出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# 日誌記錄函式
log_info() {
    echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}

# 檢查 CPU 負載
check_cpu() {
    # 使用 uptime 獲取 1 分鐘平均負載
    load=$(uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | xargs)

    # 獲取 CPU 核心數
    cores=$(nproc)

    # 計算負載與核心數的比例
    load_ratio=$(echo "$load / $cores" | bc -l | cut -d. -f1)

    if [[ $load_ratio -ge 1 ]]; then
        log_warn "CPU 負載過高: ${load} (核心數: ${cores})"
        return 1
    else
        log_info "CPU 負載正常: ${load}"
        return 0
    fi
}

# 檢查記憶體使用率
check_memory() {
    # 使用 free 命令並過濾出可用記憶體百分比
    mem_total=$(free | awk '/^Mem:/ {print $2}')
    mem_avail=$(free | awk '/^Mem:/ {print $7}')

    # 計算使用百分比
    mem_used_pct=$(echo "scale=2; ($mem_total - $mem_avail) * 100 / $mem_total" | bc)

    if (( $(echo "$mem_used_pct > 90" | bc -l) )); then
        log_warn "記憶體使用率過高: ${mem_used_pct}%"
        return 1
    else
        log_info "記憶體使用率正常: ${mem_used_pct}%"
        return 0
    fi
}

# 檢查根目錄磁碟空間
check_disk() {
    # 使用 df 獲取根目錄使用百分比
    disk_pct=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')

    if [[ $disk_pct -ge 90 ]]; then
        log_warn "根目錄磁碟空間不足: ${disk_pct}%"
        return 1
    else
        log_info "根目錄磁碟空間正常: ${disk_pct}%"
        return 0
    fi
}

# 主執行流程
main() {
    log_info "開始系統監控檢查..."

    local exit_code=0

    check_cpu || exit_code=1
    check_memory || exit_code=1
    check_disk || exit_code=1

    if [[ $exit_code -ne 0 ]]; then
        log_error "監控檢查發現異常,請盡快處理!"
        exit 1
    else
        log_info "所有監控項目均正常。"
        exit 0
    fi
}

# 執行主函式
main "$@"

常見問題與解決方案

1. 為什麼要使用 set -euo pipefail

許多 Bash 腳本遺漏了這行設定,導致潛在風險。例如,若缺少 -e,當 check_cpu 函式中的某個計算命令失敗時,腳本可能會繼續執行後續的 check_memory,導致最終報告的結果完全錯誤。pipefail 則特別重要,因為在 Bash 預設行為中,管線(pipe)的成功與否取決於最後一個命令。若前一個命令失敗但最後一個成功,腳本會誤以為一切正常。加上 pipefail 後,只要管線中任何環節失敗,整個管線即視為失敗,觸發 -e 的退出機制。

2. 如何處理 bc 命令的依賴問題?

範例中使用了 bc 進行浮點數運算。然而,在某些極簡化的 Linux 發行版或 Docker 容器中,bc 可能未被預設安裝。若環境中無法使用 bc,可以改用 awk 來進行計算。例如,將 echo "scale=2; ..." | bc 替換為 awk "BEGIN {printf \"%.2f\", ($mem_total - $mem_avail) * 100 / $mem_total}"。這能提高腳本的相容性,減少對額外軟體包的依賴。

小結

撰寫健壯的 Bash 腳本並非只是讓程式碼跑起來,更在於對錯誤的預判與處理。透過本文介紹的 set -euo pipefail、函式化結構以及明確的日誌輸出,我們可以建立出既專業又易於維護的監控工具。建議讀者將此腳本加入 cron 定時任務,並配合 mailcurl 發送告警通知,即可構建出一套輕量級卻高效的自動化監控系統。