【Linux从入门到精通】第30篇:综合案例:编写一个Linux系统体检脚本

目录

一、需求分析:这个脚本要做什么?

输出效果预览

二、技术选型与用到的知识

三、分步实现

[3.1 第一步:彩色输出函数](#3.1 第一步:彩色输出函数)

[3.2 第二步:指标检测函数](#3.2 第二步:指标检测函数)

[3.3 第三步:主流程与汇总](#3.3 第三步:主流程与汇总)

四、知识点串联:复习清单

五、完整脚本

六、测试与验证

七、扩展建议

八、阶段总结

九、下篇预告


一、需求分析:这个脚本要做什么?

日常运维中最常被问到的一句话:"服务器怎么样了?"

与其每次手动敲uptimefree -hdf -h,不如写一个脚本,一键输出完整报告。我们的脚本需要满足:

  1. 全面:CPU、内存、磁盘、网络四大指标一个不少

  2. 直观:正常/警告/严重用颜色区分,一眼能看出问题

  3. 可靠:指标有阈值,超过阈值告警并返回非0退出码(可接入监控系统)

  4. 可维护:用函数模块化编写,方便后续扩展

输出效果预览

text

复制代码
==========================================
        Linux 系统体检报告
        主机: webserver-01
        时间: 2026-04-28 15:30:00
==========================================

  [✓] CPU负载: 0.52 (1min) / 0.48 (5min) / 0.45 (15min)
  [✓] 内存使用率: 45.2% (已用: 3.5G / 总: 7.7G)
  [!] 磁盘 / 使用率: 82.5% (已用: 38G / 总: 46G)
  [✗] 磁盘 /data 使用率: 95.3% (已用: 92G / 总: 96.6G)
  [!] 网络连接数: 1523 (超过阈值: 1000)

==========================================
  检查项: 总计 5 项
  正常: 2 项  警告: 2 项  严重: 1 项
==========================================

二、技术选型与用到的知识

功能模块 用到的前篇知识 关键命令/语法
函数封装 第26篇(函数与模块化) 函数定义、local变量、return
彩色输出 第26篇(公共函数库) ANSI转义序列
CPU检测 第20篇(性能监控) uptime、nproc
内存检测 第20篇(性能监控) free
磁盘检测 第13篇(磁盘管理) df
网络检测 第18篇(网络配置) ss
条件判断 第23-24篇(条件判断) if、数字比较
阈值设计 第22篇(变量) 变量默认值
退出码 第12篇(进程控制) exit返回状态

三、分步实现

3.1 第一步:彩色输出函数

从第26篇的函数库借鉴彩色输出逻辑,作为脚本的"基础设施":

bash

复制代码
# 颜色定义
readonly COLOR_GREEN='\033[32m'
readonly COLOR_YELLOW='\033[33m'
readonly COLOR_RED='\033[31m'
readonly COLOR_RESET='\033[0m'

# 彩色输出函数
green()  { echo -e "${COLOR_GREEN}$@${COLOR_RESET}"; }
yellow() { echo -e "${COLOR_YELLOW}$@${COLOR_RESET}"; }
red()    { echo -e "${COLOR_RED}$@${COLOR_RESET}"; }

3.2 第二步:指标检测函数

CPU检测(涉及第20篇的性能监控知识):

bash

复制代码
check_cpu() {
    # 获取1分钟、5分钟、15分钟的负载
    local loadavg
    loadavg=$(uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1, $2, $3}')
    local load1=$(echo "$loadavg" | awk '{print $1}')
    local load5=$(echo "$loadavg" | awk '{print $2}')
    local load15=$(echo "$loadavg" | awk '{print $3}')

    # 获取CPU核心数(阈值是核心数的百分比)
    local cores
    cores=$(nproc)
    local threshold_warn=$(echo "$cores * 0.7" | bc | cut -d. -f1)   # 70%
    local threshold_crit=$(echo "$cores * 0.9" | bc | cut -d. -f1)   # 90%

    # 判断
    if (( $(echo "$load1 > $threshold_crit" | bc -l) )); then
        red "  [✗] CPU负载: ${loadavg} (阈值: ${threshold_crit})"
        return 2
    elif (( $(echo "$load1 > $threshold_warn" | bc -l) )); then
        yellow "  [!] CPU负载: ${loadavg} (阈值: ${threshold_warn})"
        return 1
    else
        green "  [✓] CPU负载: ${loadavg}"
        return 0
    fi
}

注意 :此处负载阈值使用bc做浮点比较(Bash原生的(( ))只支持整数)。如果你的系统没有bc,可以用apt install bc安装,或改用整数运算(负载×100与阈值×100比较)。

内存检测(涉及第20篇的性能监控):

bash

复制代码
check_memory() {
    local threshold_warn=80
    local threshold_crit=90

    # 计算使用率
    local total used available usage
    read total used available < <(free -m | awk '/^Mem/ {print $2, $3, $7}')
    usage=$(( (total - available) * 100 / total ))

    # 格式化输出
    local total_h=$(echo "scale=1; $total/1024" | bc)
    local used_h=$(echo "scale=1; $used/1024" | bc)

    if [[ $usage -ge $threshold_crit ]]; then
        red "  [✗] 内存使用率: ${usage}% (可用: $((available))M / 总: ${total_h}G)"
        return 2
    elif [[ $usage -ge $threshold_warn ]]; then
        yellow "  [!] 内存使用率: ${usage}% (可用: $((available))M / 总: ${total_h}G)"
        return 1
    else
        green "  [✓] 内存使用率: ${usage}% (已用: ${used_h}G / 总: ${total_h}G)"
        return 0
    fi
}

为什么用available而不是free? 第20篇讲过,free列是未分配内存,available才是应用程序真正可用的内存(含可回收缓存)。以available计算使用率才符合实际。

磁盘检测(涉及第13篇的磁盘管理):

这里需要遍历多个挂载点。为了避免信息冗余,我们设置一个"最小使用率"阈值,低于它的磁盘直接跳过不展示:

bash

复制代码
check_disks() {
    local threshold_warn=80
    local threshold_crit=90
    local min_display=30   # 使用率低于此值的磁盘不显示(减少噪音)
    local errors=0

    # 遍历所有真实磁盘挂载点(排除虚拟文件系统)
    while IFS= read -r line; do
        local mount_point=$(echo "$line" | awk '{print $6}')
        local usage=$(echo "$line" | awk '{print $5}' | sed 's/%//')
        local total=$(echo "$line" | awk '{print $2}')
        local used=$(echo "$line" | awk '{print $3}')

        # 跳过使用率低于显示阈值且状态正常的磁盘
        [[ $usage -lt $min_display ]] && continue

        if [[ $usage -ge $threshold_crit ]]; then
            red "  [✗] 磁盘 ${mount_point} 使用率: ${usage}% (已用: ${used} / 总: ${total})"
            ((errors++))
        elif [[ $usage -ge $threshold_warn ]]; then
            yellow "  [!] 磁盘 ${mount_point} 使用率: ${usage}% (已用: ${used} / 总: ${total})"
            ((errors++))
        else
            green "  [✓] 磁盘 ${mount_point} 使用率: ${usage}% (已用: ${used} / 总: ${total})"
        fi
    done < <(df -h | grep '^/dev/')
}

这个函数从df -h输出中筛选以/dev/开头的行(真实磁盘),逐行提取挂载点和使用率,与阈值比较后决定用哪种颜色输出。

网络检测(涉及第18篇的网络配置):

网络指标有很多,这里选了"ESTABLISHED连接数"------它是服务器并发负载的直接反映:

bash

复制代码
check_network() {
    local threshold_warn=1000
    local threshold_crit=5000

    # 统计已建立的TCP连接数
    local conn_count
    conn_count=$(ss -tan state established | tail -n +2 | wc -l)

    if [[ $conn_count -ge $threshold_crit ]]; then
        red "  [✗] 网络连接数: ${conn_count} (严重阈值: ${threshold_crit})"
        return 2
    elif [[ $conn_count -ge $threshold_warn ]]; then
        yellow "  [!] 网络连接数: ${conn_count} (超过阈值: ${threshold_warn})"
        return 1
    else
        green "  [✓] 网络连接数: ${conn_count}"
        return 0
    fi
}

3.3 第三步:主流程与汇总

主流程将所有检查串联起来,在脚本末尾输出汇总统计和退出码:

bash

复制代码
main() {
    echo "=========================================="
    echo "        Linux 系统体检报告"
    echo "        主机: $(hostname)"
    echo "        时间: $(date '+%Y-%m-%d %H:%M:%S')"
    echo "=========================================="
    echo ""

    local total=0 ok=0 warn=0 crit=0

    # 逐一检查
    local results
    results="check_cpu; check_memory; check_disks; check_network"
    while IFS=';' read -r func; do
        [[ -z "$func" ]] && continue
        $func
        local ret=$?
        ((total++))
        case $ret in
            0) ((ok++)) ;;
            1) ((warn++)) ;;
            2) ((crit++)) ;;
        esac
    done <<< "$results"

    echo ""
    echo "=========================================="
    echo "  检查项: 总计 ${total} 项"
    echo "  正常: ${ok} 项  警告: ${warn} 项  严重: ${crit} 项"
    echo "=========================================="

    # 退出码:有严重问题返回2,有警告返回1,全正常返回0
    [[ $crit -gt 0 ]] && exit 2
    [[ $warn -gt 0 ]] && exit 1
    exit 0
}

main

四、知识点串联:复习清单

在编写脚本的过程中,我们实际运用了以下知识:

篇目 知识点 在脚本中的应用
第12篇 退出码与exit 脚本根据严重程度返回0/1/2
第13篇 df磁盘查询 df -h配合grep过滤真实磁盘
第18篇 ss网络统计 ss -tan state established统计TCP连接
第20篇 uptimefreenproc CPU负载、内存使用、CPU核心数
第22篇 变量默认值与字符串处理 阈值变量、readonly常量
第23篇 条件判断 if-elif-else[[ ]]数值比较
第24篇 case分支 根据返回值统计ok/warn/crit数量
第25篇 while read循环 遍历df输出、遍历检查函数列表
第26篇 函数与模块化 check_cpu等独立函数
第29篇 awk字段处理 提取uptime输出中的负载值

关键模式回顾

  • while IFS= read -r line; do ... done < <(command)------安全地逐行读取命令输出(第25篇)

  • grep '^/dev/'------过滤出真实磁盘(第27篇的正则行首锚定)

  • 函数返回值用$(())echo配合(第26篇的$()捕获)

五、完整脚本

将以上所有代码整合如下:

bash

复制代码
#!/bin/bash
# system_health.sh - Linux 系统体检脚本
# 用法: ./system_health.sh

set -euo pipefail   # 严格模式:遇错退出 / 未定义变量报错 / 管道错误传递

# ========== 颜色定义 ==========
readonly COLOR_GREEN='\033[32m'
readonly COLOR_YELLOW='\033[33m'
readonly COLOR_RED='\033[31m'
readonly COLOR_RESET='\033[0m'

green()  { echo -e "${COLOR_GREEN}$@${COLOR_RESET}"; }
yellow() { echo -e "${COLOR_YELLOW}$@${COLOR_RESET}"; }
red()    { echo -e "${COLOR_RED}$@${COLOR_RESET}"; }

# ========== 配置阈值 ==========
readonly CPU_WARN=70
readonly CPU_CRIT=90
readonly MEM_WARN=80
readonly MEM_CRIT=90
readonly DISK_WARN=80
readonly DISK_CRIT=90
readonly DISK_MIN_DISPLAY=30   # 使用率低于此值的磁盘不显示
readonly NET_WARN=1000
readonly NET_CRIT=5000

# ========== CPU检测 ==========
check_cpu() {
    local loadavg
    loadavg=$(uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1, $2, $3}')
    local load1=$(echo "$loadavg" | awk '{print $1}')
    local load5=$(echo "$loadavg" | awk '{print $2}')
    local load15=$(echo "$loadavg" | awk '{print $3}')

    local cores
    cores=$(nproc)
    local threshold_warn=$(echo "$cores * $CPU_WARN / 100" | bc | cut -d. -f1)
    local threshold_crit=$(echo "$cores * $CPU_CRIT / 100" | bc | cut -d. -f1)

    if (( $(echo "$load1 > $threshold_crit" | bc -l) )); then
        red "  [✗] CPU负载: ${loadavg} (阈值: ${threshold_crit})"
        return 2
    elif (( $(echo "$load1 > $threshold_warn" | bc -l) )); then
        yellow "  [!] CPU负载: ${loadavg} (阈值: ${threshold_warn})"
        return 1
    else
        green "  [✓] CPU负载: ${loadavg}"
        return 0
    fi
}

# ========== 内存检测 ==========
check_memory() {
    local total used available usage
    read total used available < <(free -m | awk '/^Mem/ {print $2, $3, $7}')
    usage=$(( (total - available) * 100 / total ))

    local total_h=$(echo "scale=1; $total/1024" | bc)
    local used_h=$(echo "scale=1; $used/1024" | bc)

    if [[ $usage -ge $MEM_CRIT ]]; then
        red "  [✗] 内存使用率: ${usage}% (可用: $((available))M / 总: ${total_h}G)"
        return 2
    elif [[ $usage -ge $MEM_WARN ]]; then
        yellow "  [!] 内存使用率: ${usage}% (可用: $((available))M / 总: ${total_h}G)"
        return 1
    else
        green "  [✓] 内存使用率: ${usage}% (已用: ${used_h}G / 总: ${total_h}G)"
        return 0
    fi
}

# ========== 磁盘检测 ==========
check_disks() {
    local errors=0

    while IFS= read -r line; do
        local mount_point=$(echo "$line" | awk '{print $6}')
        local usage=$(echo "$line" | awk '{print $5}' | sed 's/%//')
        local total=$(echo "$line" | awk '{print $2}')
        local used=$(echo "$line" | awk '{print $3}')

        [[ $usage -lt $DISK_MIN_DISPLAY ]] && continue

        if [[ $usage -ge $DISK_CRIT ]]; then
            red "  [✗] 磁盘 ${mount_point} 使用率: ${usage}% (已用: ${used} / 总: ${total})"
            ((errors++))
        elif [[ $usage -ge $DISK_WARN ]]; then
            yellow "  [!] 磁盘 ${mount_point} 使用率: ${usage}% (已用: ${used} / 总: ${total})"
            ((errors++))
        else
            green "  [✓] 磁盘 ${mount_point} 使用率: ${usage}% (已用: ${used} / 总: ${total})"
        fi
    done < <(df -h | grep '^/dev/')
}

# ========== 网络检测 ==========
check_network() {
    local conn_count
    conn_count=$(ss -tan state established | tail -n +2 | wc -l)

    if [[ $conn_count -ge $NET_CRIT ]]; then
        red "  [✗] 网络连接数: ${conn_count} (严重阈值: ${NET_CRIT})"
        return 2
    elif [[ $conn_count -ge $NET_WARN ]]; then
        yellow "  [!] 网络连接数: ${conn_count} (超过阈值: ${NET_WARN})"
        return 1
    else
        green "  [✓] 网络连接数: ${conn_count}"
        return 0
    fi
}

# ========== 主流程 ==========
main() {
    echo "=========================================="
    echo "        Linux 系统体检报告"
    echo "        主机: $(hostname)"
    echo "        时间: $(date '+%Y-%m-%d %H:%M:%S')"
    echo "=========================================="
    echo ""

    local total=0 ok=0 warn=0 crit=0

    # 依次执行各检查函数
    local funcs=("check_cpu" "check_memory" "check_disks" "check_network")
    for func in "${funcs[@]}"; do
        $func
        local ret=$?
        ((total++))
        case $ret in
            0) ((ok++)) ;;
            1) ((warn++)) ;;
            2) ((crit++)) ;;
        esac
    done

    echo ""
    echo "=========================================="
    echo "  检查项: 总计 ${total} 项"
    echo "  正常: ${ok} 项  警告: ${warn} 项  严重: ${crit} 项"
    echo "=========================================="

    [[ $crit -gt 0 ]] && exit 2
    [[ $warn -gt 0 ]] && exit 1
    exit 0
}

main "$@"

六、测试与验证

bash

复制代码
# 1. 保存脚本并赋予执行权限
chmod +x system_health.sh

# 2. 直接执行
./system_health.sh

# 3. 检查退出码
echo "退出码: $?"   # 0=正常 1=警告 2=严重

# 4. 模拟高负载(在另一个终端执行,然后运行脚本看效果)
stress --cpu 4 --timeout 30 &

如果系统没有bc命令,脚本中的浮点比较会失败。安装方法:

bash

复制代码
sudo apt install bc -y     # Ubuntu/Debian
sudo yum install bc -y     # CentOS 7
sudo dnf install bc -y     # CentOS 8+/Fedora

七、扩展建议

这个脚本是一个基础框架,你可以根据实际需求扩展:

扩展方向 实现方式
更多指标 添加check_swap(检查Swap使用率)、check_process(检查关键进程)
邮件告警 检测到严重问题时调用mailcurl发送通知
定时执行 配置crontab每小时执行一次,输出到日志
JSON输出 改为JSON格式,方便被监控系统(如Zabbix)采集
支持远程 通过SSH在多台服务器上执行
去掉bc依赖 负载比较可以用awk的浮点能力替代(`echo "load1 threshold"

八、阶段总结

至此,Shell脚本编程的10篇文章(第21-30篇)全部完成。回顾整个阶段,我们掌握了:

篇目 核心技能
21-22 变量定义、字符串处理、环境变量
23-24 条件判断、if-else、case分支
25 for/while/until循环
26 函数与模块化、source引入库
27-29 grep/sed/awk文本处理三剑客
30 综合实战:系统体检脚本

你已经具备了将重复性工作自动化的能力。从现在开始,遇到任何需要重复操作两次以上的任务,都可以问自己:"我能用一个脚本解决它吗?"

九、下篇预告

从下一篇开始,我们将进入第四阶段------服务器应用与生产环境实战 。第一篇将聚焦服务器安全的第一道防线:防火墙

《防火墙漫谈------iptables与firewalld防护指南》将带你了解:

  • iptables的四表五链是什么

  • 用firewalld轻松管理端口开放

  • 紧急情况下如何一键"拔网线"


延伸思考set -euo pipefail放在脚本开头是一种"严格模式",它让脚本在任何命令失败、使用未定义变量、管道中间命令失败时立即退出。这能避免很多"脚本继续执行导致更大破坏"的悲剧。但也要注意,有些命令的返回值不一定代表"错误"(如grep没找到匹配会返回1),此时可以加|| true显式忽略。

相关推荐
草履虫君1 小时前
wsl的装龙虾方式,接入飞书的时候需要在wsl环境装飞书插件,那么node模式接入飞书,需要怎么装飞书插件?
服务器·经验分享·飞书
海的预约1 小时前
Bootloader应用分析
linux·运维·服务器
时空未宇1 小时前
海鸥派顺利运行YOLO11S
linux·运维·服务器
ElevenS_it1881 小时前
日志在哪里找?分布式环境下日志采集断裂的5个排查路径
运维·网络·分布式
半壶清水1 小时前
ubuntu中部署开源交换机模拟器bmv2详细步骤
linux·运维·网络·网络协议·tcp/ip·ubuntu
j_xxx404_2 小时前
Linux:深入解析ELF文件结构
linux·运维·服务器
互联网推荐官2 小时前
上海软件定制开发与数字化建设:D-coding 全平台应用架构及实施指南
大数据·运维
ShineWinsu2 小时前
对于Linux:进程间通信IPC(共享内存)的解析
linux·服务器·面试·笔试·进程·共享内存·ipc
代码中介商2 小时前
Linux 进程间通信:共享内存与消息队列完全指南
linux·运维·服务器