目录
[3.1 第一步:彩色输出函数](#3.1 第一步:彩色输出函数)
[3.2 第二步:指标检测函数](#3.2 第二步:指标检测函数)
[3.3 第三步:主流程与汇总](#3.3 第三步:主流程与汇总)
一、需求分析:这个脚本要做什么?
日常运维中最常被问到的一句话:"服务器怎么样了?"
与其每次手动敲uptime、free -h、df -h,不如写一个脚本,一键输出完整报告。我们的脚本需要满足:
-
全面:CPU、内存、磁盘、网络四大指标一个不少
-
直观:正常/警告/严重用颜色区分,一眼能看出问题
-
可靠:指标有阈值,超过阈值告警并返回非0退出码(可接入监控系统)
-
可维护:用函数模块化编写,方便后续扩展
输出效果预览
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篇 | uptime、free、nproc |
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(检查关键进程) |
| 邮件告警 | 检测到严重问题时调用mail或curl发送通知 |
| 定时执行 | 配置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显式忽略。