Shell脚本自动化实战:从基础命令到系统监控脚本开发

发布时间:2026/7/29 3:00:51
Shell脚本自动化实战:从基础命令到系统监控脚本开发 1. 为什么说Shell脚本是程序员的“瑞士军刀”如果你在Linux或macOS的终端里敲过命令那你已经半只脚踏入了Shell脚本的世界。很多人觉得这玩意儿就是一堆命令的堆砌没什么技术含量但恰恰相反Shell脚本是连接用户与操作系统内核最直接、最高效的桥梁堪称运维、开发乃至数据分析领域的“瑞士军刀”。我见过太多同事面对重复性的文件整理、日志分析、服务部署还在手动一行行敲命令或者用图形界面点点点效率低下还容易出错。而一个几十行的脚本就能把这些工作自动化解放双手把精力留给更有创造性的思考。Shell脚本的核心价值在于“自动化”和“胶水”。它能把系统命令、文本处理工具如grep、awk、sed、甚至其他编程语言如Python的执行结果像胶水一样粘合起来形成一个完整的工作流。比如你每天需要从十几台服务器上拉取日志过滤错误信息汇总成报告并邮件发送。手动做半小时起步。写个Shell脚本设置个定时任务cron job从此每天早晨喝咖啡时报告就在邮箱里了。这种从重复劳动中解脱出来的感觉是提升工程师幸福感的关键。网上教程很多但要么过于零散只讲几个命令要么一上来就抛出复杂的语法让人望而生畏。我结合自己十多年的踩坑经验打算系统性地拆解Shell脚本目标不是让你死记硬背语法而是建立一种“脚本化思维”。看到任何重复性任务第一反应就是“能不能写个脚本搞定它” 这篇内容会从最基础的“Hello World”开始一直讲到脚本调试、错误处理、性能优化等高级话题并穿插大量真实工作场景中的案例和那些官方手册里不会写的“坑”。无论你是刚接触Linux的新手还是想系统提升自动化能力的老兵相信都能找到你需要的东西。2. Shell脚本学习路径全景图与核心思想学习任何技术最怕没有地图盲目乱撞。Shell脚本的学习我总结为“三层金字塔”结构基础命令层、流程控制层、工程实践层。绝大多数人卡在第一层和第二层之间因为命令看似简单组合起来却千变万化。2.1 基础命令层你的工具箱这一层是基石目标是熟练使用二三十个最核心的命令和概念。别被上百个命令吓到常用的就那些文件操作ls,cd,cp,mv,rm,mkdir,find。光是find命令配合-exec参数就能解决大半的文件查找与批量处理问题。文本处理三剑客grep搜索、sed流编辑、awk文本分析。这是Shell脚本的灵魂数据处理能力大半靠它们。初期不必深究awk的全部语法学会用awk ‘{print $1}’提取第一列就很有用。系统状态ps,top,df,du,free。写监控脚本必备。权限与用户chmod,chown,sudo。理解755、644这些数字背后的二进制含义rwx是安全编写脚本的前提。注意很多人喜欢死记硬背命令参数。我的建议是学会用man命令如man grep和--help参数。更重要的是理解每个命令的“输入”和“输出”。在Shell的世界里几乎所有命令都默认从“标准输入”读取数据将结果输出到“标准输出”将错误输出到“标准错误”。理解了这个“流”的概念你才能明白管道符|为何如此强大。2.2 流程控制层赋予脚本逻辑有了工具你需要用逻辑把它们串起来。这是Shell编程的核心。变量与参数如何定义变量VARvalue如何使用它$VAR如何传递外部参数$1,$2,$。这里有个大坑等号两边不能有空格VAR value是错误的。条件判断if...then...elif...else...fi。判断条件用[ ]或[[ ]]后者更强大支持正则匹配。记住字符串比较用数字比较用-eq文件判断用-f是否存在文件。循环for i in list; do ... done和while condition; do ... done。for循环常用于遍历文件列表while循环常用于读文件或等待某个条件。函数将一段代码封装起来function_name() { ... }。好的脚本一定是模块化的用函数来提高可读性和复用性。2.3 工程实践层从脚本到可靠工具这是区分“能用”和“好用”的关键。很多脚本跑一次就扔而工程化的脚本应该像产品一样可靠。调试与错误处理使用set -e遇到错误立即退出、set -u使用未定义变量时报错、set -x打印执行的每一行命令。在关键操作后检查上一条命令的返回值$?不为0则表示失败。信号处理用trap命令捕获CtrlCSIGINT等信号在脚本被中断时执行清理工作如删除临时文件。代码风格与文档统一的缩进推荐两个空格有意义的变量名在文件开头用注释说明脚本用途、参数、作者和示例。这对自己和同事都是一种尊重。性能考量避免在循环中调用外部命令特别是管道尽量使用Shell内置功能。处理大文件时考虑用awk或sed替代while read line循环。我的学习建议是按层推进每层都通过实际小项目巩固。比如学完基础命令就写个脚本统计当前目录下各种类型文件的个数学完流程控制就写个备份脚本可以按日期命名备份文件学完工程实践就给备份脚本加上日志记录和错误报警功能。3. 从零到一你的第一个专业级Shell脚本剖析光说不练假把式。我们从一个具体的、有实用价值的脚本开始逐行拆解把上面提到的概念具象化。假设我们有这样一个需求监控指定目录的大小如果超过设定阈值则自动清理最早的文件并发送邮件通知。这是一个非常经典的运维场景。3.1 脚本框架与参数解析一个健壮的脚本应该从清晰的框架开始。我们首先定义脚本的元信息和参数处理。#!/bin/bash # # 脚本名称directory_cleaner.sh # 功能描述监控目录大小超限后清理最旧文件并告警 # 作者Your Name # 使用方法./directory_cleaner.sh /path/to/dir 1024 adminexample.com # 参数说明$1 - 监控目录路径 # $2 - 目录大小阈值单位MB # $3 - 告警邮件接收地址 # set -euo pipefail # 严格模式遇错退出未定义变量报错管道中任意命令失败则整个管道失败 # 参数检查 if [ $# -ne 3 ]; then echo 错误参数数量不正确。 echo 用法$0 目录路径 阈值(MB) 邮箱地址 exit 1 fi TARGET_DIR$1 SIZE_LIMIT_MB$2 ALERT_EMAIL$3代码解读与避坑第一行#!/bin/bash是shebang告诉系统用哪个解释器执行。虽然有时省略也能运行但显式声明是好习惯避免因默认Shell不同如dash导致语法错误。set -euo pipefail是编写可靠脚本的“黄金法则”。-e确保任何命令失败返回非0脚本立即停止防止错误累积。-u防止使用未赋值的变量常见错误来源。-o pipefail确保管道命令中任何一个失败整个管道返回值就视为失败。这行代码能帮你避免很多隐蔽的bug。参数检查if [ $# -ne 3 ]必不可少。$#代表参数个数。缺少检查脚本可能用空变量去操作导致破坏性后果比如rm -rf $undefined_var/。变量赋值时路径、邮箱这类可能包含空格的参数一定要用双引号引起来如“$1”。这是防止文件名中有空格导致命令被错误分割的关键。3.2 核心逻辑实现监控与清理接下来我们实现核心的目录大小检查和清理逻辑。# 转换阈值为KBdu命令默认输出KB SIZE_LIMIT_KB$(( SIZE_LIMIT_MB * 1024 )) # 获取目录当前大小单位KB仅统计文件大小排除子目录本身占用的元数据 CURRENT_SIZE_KB$(du -sk “$TARGET_DIR” | cut -f1) # 记录日志函数 log_message() { local log_level$1 local message$2 echo “[$(date ‘%Y-%m-%d %H:%M:%S’)] [$log_level] $message” } log_message “INFO” “开始检查目录$TARGET_DIR当前大小${CURRENT_SIZE_KB}KB阈值${SIZE_LIMIT_KB}KB” if [ “$CURRENT_SIZE_KB” -gt “$SIZE_LIMIT_KB” ]; then log_message “WARN” “目录大小超出阈值开始清理最旧文件...” # 进入目标目录防止后续rm命令因路径问题误删 cd “$TARGET_DIR” || { log_message “ERROR” “无法进入目录 $TARGET_DIR”; exit 1; } # 查找并删除最旧的一个普通文件排除目录 OLDEST_FILE$(find . -maxdepth 1 -type f -printf ‘%T %p\n’ | sort | head -n 1 | cut -d‘ ’ -f2-) if [ -n “$OLDEST_FILE” ]; then log_message “INFO” “正在删除最旧文件$OLDEST_FILE” rm -f “$OLDEST_FILE” # 使用-f强制删除避免交互提示 # 重新计算大小 CURRENT_SIZE_KB$(du -sk “$TARGET_DIR” | cut -f1) log_message “INFO” “清理完成。目录新大小${CURRENT_SIZE_KB}KB” # 准备邮件内容 MAIL_SUBJECT“【目录清理告警】$TARGET_DIR 空间已清理” MAIL_BODY“监控目录$TARGET_DIR 原大小${CURRENT_SIZE_KB}KB超过阈值 ${SIZE_LIMIT_KB}KB 已删除文件$OLDEST_FILE 清理后大小${CURRENT_SIZE_KB}KB 操作时间$(date)” # 发送邮件假设系统已配置好mail命令或sendmail echo “$MAIL_BODY” | mail -s “$MAIL_SUBJECT” “$ALERT_EMAIL” 2/dev/null || log_message “ERROR” “邮件发送失败请检查邮件配置。” else log_message “ERROR” “未在目录中找到可清理的普通文件。” fi else log_message “INFO” “目录大小正常无需清理。” fi关键点解析与实战技巧du -sk与cut的组合du -s是总计-k是以KB为单位。cut -f1是取第一列即大小数值。这是一种经典的“命令管道”提取特定信息的模式。find命令的妙用find . -maxdepth 1 -type f查找当前目录非递归下的所有普通文件。-printf ‘%T %p\n’是GNU find的特性以YYYY-MM-DDHH:MM:SS格式打印文件最后修改时间然后空格再跟文件名。这样排序时时间戳在前的就是最旧的文件。sort默认按字符串升序排列时间戳格式正好符合。head -n 1取第一行cut -d‘ ’ -f2-以空格为分隔符取第二列到最后一列即文件名。这个命令链是高效查找最旧文件的核心。cd命令的错误处理cd “$TARGET_DIR” || { ...; exit 1; }。如果cd失败目录不存在或无权限||后面的命令块会执行记录错误并退出。这是利用Shell逻辑运算符进行错误处理的典型做法。邮件发送的容错mail命令不一定在所有系统都可用。2/dev/null将错误输出重定向到空设备||后面跟一个日志记录这样即使邮件发送失败脚本也不会因此中断只是记录一个错误。在生产环境中你可能需要替换为更可靠的发送方式如使用Python的smtplib库或第三方命令行工具。日志函数自定义log_message函数统一日志格式时间、级别、信息便于后续用grep等工具分析。这是脚本可维护性的重要体现。4. 文本处理三剑客grep, sed, awk的深度应用与性能抉择Shell脚本的强大一半在于它能方便地调用像grep、sed、awk这样的专业文本处理工具。它们各有专长用对了事半功倍用错了或混用则可能成为性能瓶颈。4.1 grep模式搜索的利刃grep的核心是“过滤”。它从输入中快速找出匹配指定模式的行。基础用法grep “error” logfile.log查找包含“error”的行。常用参数-i忽略大小写。-v反向选择输出不匹配的行。-n显示匹配行的行号。-c只统计匹配的行数。-r递归搜索目录下的所有文件。-E启用扩展正则表达式等同于egrep功能更强大。高级技巧与避坑固定字符串搜索当模式是简单的字符串而非正则时使用-F或fgrep速度更快因为它不做正则解析。上下文查看-A 5After显示匹配行后5行-B 5Before显示前5行-C 5Context显示前后各5行。这在看日志时非常有用。性能陷阱在循环中对大文件反复使用grep是性能杀手。应该尽量将grep放在管道的最前面或者一次性用grep -r处理多个文件。4.2 sed流编辑器擅长“以行为单位”的编辑sed的核心是“替换”和“删除”。它按行读取输入根据规则进行修改后输出。基础替换sed ‘s/old/new/g’ file.txt。s表示替换g表示全局一行内所有匹配。默认只替换每行第一个匹配。指定行范围sed ‘10,20s/old/new/g’只替换第10到20行。删除行sed ‘/pattern/d’删除匹配pattern的行。sed ‘3d’删除第3行。原地编辑sed -i ‘s/old/new/g’ file.txt。-i选项会直接修改原文件务必先备份或测试无误后再使用这是血泪教训。实战案例批量修改配置文件中的IP地址。# 假设要将所有192.168.1.x的IP改为10.0.0.x sed -i.bak ‘s/192\.168\.1\./10.0.0./g’ /path/to/*.conf这里-i.bak会在修改前为每个文件创建一个带.bak后缀的备份是安全操作的好习惯。4.3 awk编程式的文本分析工具awk不仅仅是一个命令它是一门拥有变量、条件、循环、函数的微型编程语言特别擅长处理结构化文本如CSV、日志。基本结构awk ‘pattern { action }’ file。对文件中每一行如果匹配pattern就执行action。内置变量$0代表整行$1,$2…代表第一、第二列默认以空格或制表符分隔。NF是当前行的字段数NR是当前行号。经典示例打印第一列和第三列awk ‘{print $1, $3}’ data.txt统计文件行数awk ‘END {print NR}’ data.txt。END模式在所有行处理完后执行。计算第二列的总和awk ‘{sum $2} END {print sum}’ data.txt根据条件过滤awk ‘$3 100 {print $0}’ data.txt打印第三列大于100的行。高级用法字段分隔符与数组# 处理CSV文件逗号分隔 awk -F‘,’ ‘{print $2}’ data.csv # 统计每个IP出现的次数假设第一列是IP awk ‘{ip_count[$1]} END {for(ip in ip_count) print ip, ip_count[ip]}’ access.log-F指定字段分隔符。ip_count[$1]利用了awk的关联数组是非常强大的聚合分析功能。4.4 性能抉择与组合使用原则面对一个文本处理任务如何选择工具记住这个口诀简单查找用grep简单替换用sed列处理与计算用awk。能用内置功能就不用外部命令在Shell脚本中while read循环通常比awk慢。例如逐行处理文件并提取某列用awk ‘{print $2}’比while read line; do echo $line | cut -d‘ ’ -f2; done快得多。减少管道数量每个管道|都会创建一个新的子进程有开销。例如cat file | grep A | grep B可以合并为grep A file | grep B甚至直接用grep -E ‘A.*B|B.*A’ file如果逻辑允许。awk本身功能强大很多时候可以替代grepsedcut的组合。处理大文件时优先考虑awk和sed它们是为流式处理设计的。避免使用for i in $(cat hugefile)这会把整个文件读入内存并分词非常低效。应该用while IFS read -r line; do ... done hugefile。5. Shell脚本中的“坑”与高级调试技巧即使语法熟练写Shell脚本也难免踩坑。很多错误静默发生直到造成后果才发现。这部分分享我积累的常见“坑”和调试方法相当于给你的脚本上了保险。5.1 变量与字符串处理的经典陷阱未引用的变量导致分词和路径扩展# 错误示范 for file in $(ls *.txt); do rm $file # 如果文件名包含空格如“my file.txt”会被拆分成“my”和“file.txt”两个参数导致错误 done # 正确做法使用数组或find的-exec for file in *.txt; do rm “$file” done # 或者 find . -name “*.txt” -exec rm {} \;始终记住变量展开、命令替换、路径名生成时一定要用双引号。数字与字符串比较混淆a“10” b“2” # 错误使用字符串比较运算符 if [ “$a” “$b” ]; then ... # 这会进行字典序比较“10” “2”因为‘1’比‘2’小。 # 正确使用算术比较 if (( a b )); then ... # 双括号内进行算术运算和比较 # 或者 if [ “$a” -gt “$b” ]; then ... # -gt 是用于整数的“大于”命令替换中的尾随换行符file_count$(ls | wc -l) # wc -l 输出结果会包含换行符如“5\n” # 直接使用可能导致问题可以用命令替换echo去换行 file_count$(ls | wc -l | tr -d ‘\n’) # 或者在需要数字比较的算术上下文中Shell会自动去除尾随空白 if (( $(ls | wc -l) 10 )); then ... # 这里没问题5.2 错误处理与信号捕获脚本可能因各种原因失败命令不存在、权限不足、磁盘满、用户中断CtrlC。健壮的脚本必须处理这些情况。检查命令返回值关键命令执行后立即检查$?。cp important.txt backup/ if [ $? -ne 0 ]; then log_message “ERROR” “复制文件失败” exit 1 fi # 更简洁的写法利用逻辑运算符 cp important.txt backup/ || { log_message “ERROR” “复制失败”; exit 1; }使用trap捕获信号# 定义清理函数 cleanup() { echo “正在清理临时文件...” rm -f /tmp/myscript_temp.* exit 1 } # 注册信号处理函数当收到SIGINT (CtrlC), SIGTERM (终止信号)时执行cleanup trap cleanup INT TERM # 脚本主体... # 脚本正常退出前也可以手动移除trap trap - INT TERM这能防止脚本被意外中断后留下垃圾临时文件。5.3 高级调试技巧当脚本行为诡异时你需要像侦探一样排查。set -x与set x这是最直接的调试方法。set -x会让Shell打印出每一行实际执行的命令变量已展开。在怀疑出问题的代码块前后加上set -x和set x。输出关键变量在怀疑点插入echo “DEBUG: var$var”查看变量实际值。使用ShellCheck这是一个静态分析工具能检测出脚本中的语法问题、常见错误和不良实践。在编写完成后用shellcheck your_script.sh检查一下能避免很多低级错误。逐段测试将复杂脚本分解成函数然后单独测试每个函数。可以创建一个专门的test_函数来调用它们验证输入输出是否符合预期。5.4 安全注意事项Shell脚本权限很大一个疏忽可能造成数据丢失。执行删除操作前先echo在写rm -rf命令时可以先写成echo rm -rf “$file”运行脚本看看会删除哪些文件确认无误后再去掉echo。对用户输入保持警惕如果脚本接收用户输入作为参数或变量一定要进行验证。特别是当输入会用于命令拼接时有命令注入的风险。# 危险 user_input“/tmp; rm -rf /” rm -rf “$user_input” # 如果变量未加引号后果不堪设想。加了引号会将其视为一个整体路径安全很多但仍需验证路径合法性。 # 应对对路径参数可以用realpath、dirname等命令规范化并检查是否在允许的目录内。使用最小权限原则不要用root权限运行所有脚本。考虑是否需要sudo以及是否可以通过配置/etc/sudoers文件精细控制权限。6. 实战构建一个带配置文件和日志的系统监控脚本我们将前面所有知识融会贯通写一个更接近生产环境的实用脚本一个系统资源监控脚本它能读取外部配置文件监控CPU、内存、磁盘使用率超过阈值则记录日志并发送告警模拟并且所有行为都可配置。6.1 项目结构与配置文件设计一个好的脚本项目应该有清晰的结构。我们这样组织system_monitor/ ├── monitor.sh # 主脚本 ├── monitor.conf # 配置文件 └── logs/ # 日志目录脚本自动创建配置文件monitor.conf使用易于解析的keyvalue格式# 系统监控配置 ALERT_CPU_PERCENT80 ALERT_MEM_PERCENT85 ALERT_DISK_PERCENT90 LOG_DIR“./logs” LOG_FILE“system_monitor.log” # 是否启用模拟告警1启用0禁用 ENABLE_ALERT1 ALERT_METHOD“log” # 可选 log | echo (模拟)6.2 主脚本实现monitor.sh这个脚本展示了模块化、配置化、日志化的完整思路。#!/bin/bash set -euo pipefail # # 系统资源监控脚本 # 功能读取配置检查CPU/内存/磁盘使用率超阈值告警 # SCRIPT_DIR“$(cd “$(dirname “${BASH_SOURCE[0]}”)” pwd)” # 获取脚本所在目录的绝对路径 CONFIG_FILE“${SCRIPT_DIR}/monitor.conf” # 默认配置防止配置文件缺失 ALERT_CPU_PERCENT80 ALERT_MEM_PERCENT85 ALERT_DISK_PERCENT90 LOG_DIR“${SCRIPT_DIR}/logs” LOG_FILE“system_monitor.log” ENABLE_ALERT1 ALERT_METHOD“log” # 加载配置文件 load_config() { if [ ! -f “$CONFIG_FILE” ]; then echo “[$(date)] [WARN] 配置文件 $CONFIG_FILE 不存在使用默认配置。” 2 return 1 fi # 安全地source配置文件避免配置文件中恶意命令执行 while IFS‘’ read -r key value; do # 跳过注释行和空行 [[ “$key” ~ ^[[:space:]]*# ]] continue [[ -z “$key” ]] continue # 去除可能的空白字符 key“$(echo -e “${key}” | sed -e ‘s/^[[:space:]]*//’ -e ‘s/[[:space:]]*$//’)” value“$(echo -e “${value}” | sed -e ‘s/^[[:space:]]*//’ -e ‘s/[[:space:]]*$//’)” # 使用declare动态设置变量更安全 declare -g “$key”“$value” 2/dev/null || echo “[$(date)] [WARN] 无法设置配置项: $key” 2 done “$CONFIG_FILE” } # 初始化日志系统 init_log() { mkdir -p “$LOG_DIR” LOG_PATH“${LOG_DIR}/${LOG_FILE}” } # 日志记录函数 log() { local level“$1” local message“$2” local timestamp timestamp“$(date ‘%Y-%m-%d %H:%M:%S’)” echo “[${timestamp}] [${level}] ${message}” | tee -a “$LOG_PATH” } # 告警函数 alert() { local resource“$1” local usage“$2” local threshold“$3” local message“${resource} 使用率 ${usage}% 超过阈值 ${threshold}%” if [ “$ENABLE_ALERT” -eq 1 ]; then case “$ALERT_METHOD” in “log”) log “ALERT” “$message” ;; “echo”) echo “【告警】$message” 2 # 输出到标准错误便于区分 ;; *) log “ERROR” “未知的告警方式: $ALERT_METHOD” ;; esac fi } # 获取CPU使用率取1秒内的平均使用率简单方法 get_cpu_usage() { # 读取/proc/stat计算空闲时间和总时间差值的比例 local cpu_line cpu_line$(grep ‘^cpu ‘ /proc/stat) local idle1 idle2 total1 total2 # 解析user nice system idle iowait irq softirq steal guest guest_nice # 我们主要关注idle第4列和iowait第5列实际上idleiowait算作空闲。 # 更常见的简化使用top或mpstat这里用vmstat取一个采样 # 为了可移植性使用一个简单但近似的命令注意这在不同系统上可能不准生产环境建议用更精确的方法 local usage usage$(top -bn1 | grep “%Cpu(s)” | awk ‘{print 100 - $8}’) # 如果上述命令失败使用备选方案 if [[ ! “$usage” ~ ^[0-9](\.[0-9])?$ ]]; then usage$(vmstat 1 2 | tail -1 | awk ‘{print 100 - $15}’) fi echo “${usage%.*}” # 取整 } # 获取内存使用率 get_mem_usage() { local mem_info mem_info$(free | grep Mem) local total used total$(echo “$mem_info” | awk ‘{print $2}’) used$(echo “$mem_info” | awk ‘{print $3}’) # 计算百分比 echo $(( used * 100 / total )) } # 获取根分区磁盘使用率 get_disk_usage() { df -h / | tail -1 | awk ‘{print $5}’ | sed ‘s/%//’ } # 主监控逻辑 main() { load_config init_log log “INFO” “ 系统监控开始 local cpu_usage mem_usage disk_usage cpu_usage$(get_cpu_usage) log “INFO” “CPU使用率: ${cpu_usage}%” if [ “$cpu_usage” -gt “$ALERT_CPU_PERCENT” ]; then alert “CPU” “$cpu_usage” “$ALERT_CPU_PERCENT” fi mem_usage$(get_mem_usage) log “INFO” “内存使用率: ${mem_usage}%” if [ “$mem_usage” -gt “$ALERT_MEM_PERCENT” ]; then alert “内存” “$mem_usage” “$ALERT_MEM_PERCENT” fi disk_usage$(get_disk_usage) log “INFO” “磁盘使用率: ${disk_usage}%” if [ “$disk_usage” -gt “$ALERT_DISK_PERCENT” ]; then alert “磁盘” “$disk_usage” “$ALERT_DISK_PERCENT” fi log “INFO” “ 系统监控结束 } # 脚本入口 main “$”6.3 关键技术与经验点解析安全的配置文件加载我们没有直接用source monitor.conf而是用while read循环逐行解析。这是因为source会直接执行文件中的任何Shell代码如果配置文件被恶意篡改或编写不当可能带来安全风险。我们的方法只进行变量赋值更安全。SCRIPT_DIR的获取“$(cd “$(dirname “${BASH_SOURCE[0]}”)” pwd)”是获取脚本所在目录绝对路径的经典且可靠的方法无论从何处调用脚本都能准确定位配置文件和日志目录的相对位置。模块化与函数化每个功能加载配置、初始化日志、获取指标都封装成函数main函数清晰表达了业务流程。这大大提升了代码的可读性和可维护性。命令的兼容性与降级方案在get_cpu_usage函数中我们首先尝试用top命令如果输出格式不符或命令不存在则降级使用vmstat。在实际生产脚本中这种兼容性处理非常重要因为不同Linux发行版的命令输出可能有细微差别。日志的tee用法log函数中使用tee -a “$LOG_PATH”既将日志打印到标准输出方便实时查看又追加到日志文件中。这在调试和长期运行中非常有用。模拟告警机制通过ENABLE_ALERT和ALERT_METHOD配置项我们可以灵活控制告警行为。在测试阶段可以设为echo在生产环境可以改为调用真实的邮件、短信或API接口。这种设计使得脚本的核心监控逻辑与具体的告警实现解耦。你可以通过crontab设置这个脚本每分钟运行一次* * * * * /path/to/system_monitor/monitor.sh。一个简单的监控系统就搭建完成了。通过修改配置文件你可以轻松调整阈值、日志路径和告警方式而无需修改主脚本代码。这就是Shell脚本结合配置化思想带来的灵活性。