从入门到生产:运维工程师必备的 Shell 编程完全指南(附 10 个生产级脚本)

张开发
2026/4/14 1:05:51 15 分钟阅读

分享文章

从入门到生产:运维工程师必备的 Shell 编程完全指南(附 10 个生产级脚本)
前言作为一名运维程师Shell 编程是你必须掌握的第一门技能。它不是什么高深的编程语言但却是你日常工作中最高效的自动化工具。很多人觉得 Shell 脚本很简单不就是把命令堆在一起吗但真正写过生产环境脚本的人都知道简单的是语法要命的是规范、容错和安全。一个写得不好的 Shell 脚本轻则导致任务失败重则直接删库跑路。这篇文章会从最基础的概念讲起带你掌握 Shell 编程的核心语法然后分享 10 个在生产环境中天天在用的 Shell 脚本全部遵循行业最佳实践你可以直接拿去修改使用。一、Shell 编程基础1.1 什么是 ShellShell 是 Linux 系统的命令解释器它充当了用户和 Linux 内核之间的翻译官。你在终端输入的每一条命令都是由 Shell 解释并传递给内核执行的。Linux 系统中有多种 Shell 实现Bash最常用的默认 Shell几乎所有 Linux 发行版都预装Zsh功能更强大支持更多插件和主题Fish用户友好语法更简单Sh最原始的 Shell兼容性最好我们日常写的绝大多数脚本都是Bash 脚本因为它的兼容性最好在任何服务器上都能运行。1.2 第一个 Shell 脚本一个最简单的 Shell 脚本只需要三行#!/bin/bash # 这是注释 echo Hello, Shell!保存为hello.sh然后添加执行权限并运行chmod x hello.sh ./hello.sh关键说明第一行#!/bin/bash称为 shebang指定脚本的解释器必须写在第一行#开头的是注释不会被执行echo用于输出文本1.3 核心语法速查变量# 定义变量等号两边不能有空格 name运维工程师 age25 # 使用变量推荐加花括号 echo 我是${name}今年${age}岁 # 特殊变量 echo 脚本名$0 echo 第一个参数$1 echo 参数个数$# echo 所有参数$ echo 上一条命令的退出码$?条件判断# 基本语法注意空格 if [ 条件 ]; then # 执行语句 elif [ 条件 ]; then # 执行语句 else # 执行语句 fi # 常用条件 [ -f file.txt ] # 文件是否存在 [ -d /data ] # 目录是否存在 [ -x script.sh ] # 是否有执行权限 [ $a -eq $b ] # 等于 [ $a -ne $b ] # 不等于 [ $a -gt $b ] # 大于 [ $a -lt $b ] # 小于 [ -z $str ] # 字符串为空 [ -n $str ] # 字符串不为空循环# for循环 for i in {1..10}; do echo 第${i}次循环 done # 遍历文件 for file in *.log; do echo 处理文件${file} done # while循环 i1 while [ $i -le 10 ]; do echo 第${i}次循环 i$((i1)) done函数# 定义函数 check_service() { local service_name$1 # 局部变量 if systemctl is-active --quiet $service_name; then echo $service_name 正在运行 return 0 else echo $service_name 已停止 return 1 fi } # 调用函数 check_service nginx管道和重定向# 管道将前一个命令的输出作为后一个命令的输入 ps aux | grep nginx # 重定向 echo 日志内容 log.txt # 覆盖写入 echo 追加内容 log.txt # 追加写入 command 2 error.log # 重定向错误输出 command all.log # 重定向标准输出和错误输出二、生产级 Shell 脚本编写规范这是本文最重要的部分也是区分新手和老运维的关键。2.1 开头必须加严格模式#!/bin/bash set -euo pipefail解释set -e脚本中任何命令失败立即退出脚本set -u使用未定义的变量时立即报错退出set -o pipefail管道中任何命令失败整个管道命令视为失败2.2 永远使用绝对路径# 错误写法 cd log rm *.log # 正确写法 LOG_DIR/data/app/log cd $LOG_DIR rm $LOG_DIR/*.log2.3 所有变量都加双引号# 错误写法 rm $file # 正确写法 rm $file防止文件名或路径中包含空格时出现意外。2.4 高危命令必须加判断# 错误写法 rm -rf $DIR/* # 正确写法 if [ -n $DIR ] [ -d $DIR ]; then rm -rf $DIR/* fi2.5 脚本必须有日志LOG_FILE/var/log/script.log log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* $LOG_FILE } log 脚本开始执行2.6 调试技巧# 运行时调试 bash -x script.sh # 脚本中开启调试 set -x # 要调试的代码 set x三、运维最常用的 10 个生产级 Shell 脚本以下所有脚本都严格遵循上述规范可直接在生产环境使用。脚本 1系统健康检查脚本#!/bin/bash set -euo pipefail # 系统健康检查脚本 # 作者运维工程师 # 日期2026-04-10 LOG_FILE/var/log/system_health.log log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* $LOG_FILE } log 开始系统健康检查 # 检查CPU使用率 CPU_USAGE$(top -bn1 | grep Cpu(s) | awk {print $2} | cut -d% -f1) log CPU使用率${CPU_USAGE}% # 检查内存使用率 MEM_USAGE$(free | grep Mem | awk {printf(%.2f, $3/$2*100)}) log 内存使用率${MEM_USAGE}% # 检查磁盘使用率 log 磁盘使用率 df -h | grep -vE Filesystem|tmpfs | awk {print $6 : $5} # 检查磁盘inode使用率 log 磁盘Inode使用率 df -i | grep -vE Filesystem|tmpfs | awk {print $6 : $5} # 检查系统负载 LOAD_1$(uptime | awk {print $10} | cut -d, -f1) LOAD_5$(uptime | awk {print $11} | cut -d, -f1) LOAD_15$(uptime | awk {print $12} | cut -d, -f1) log 系统负载1分钟${LOAD_1}, 5分钟${LOAD_5}, 15分钟${LOAD_15} # 检查僵尸进程 ZOMBIE_COUNT$(ps aux | grep -c Z$) log 僵尸进程数量${ZOMBIE_COUNT} log 系统健康检查完成脚本 2日志自动清理脚本#!/bin/bash set -euo pipefail # 日志自动清理脚本 # 保留最近7天的日志 LOG_DIRS( /var/log /data/app/log /data/nginx/logs ) RETENTION_DAYS7 LOG_FILE/var/log/log_cleanup.log log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* $LOG_FILE } log 开始日志清理 for dir in ${LOG_DIRS[]}; do if [ -d $dir ]; then log 清理目录$dir find $dir -type f -name *.log -mtime $RETENTION_DAYS -delete find $dir -type f -name *.log.* -mtime $RETENTION_DAYS -delete else log 警告目录 $dir 不存在 fi done log 日志清理完成脚本 3服务状态监控与自动重启#!/bin/bash set -euo pipefail # 服务监控与自动重启脚本 SERVICES(nginx mysql docker) LOG_FILE/var/log/service_monitor.log log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* $LOG_FILE } check_and_restart() { local service$1 if systemctl is-active --quiet $service; then log $service 运行正常 else log 警告$service 已停止正在重启... if systemctl restart $service; then log $service 重启成功 else log 错误$service 重启失败 # 这里可以添加告警逻辑比如发送邮件或钉钉消息 fi fi } log 开始服务监控检查 for service in ${SERVICES[]}; do check_and_restart $service done log 服务监控检查完成脚本 4批量文件备份脚本#!/bin/bash set -euo pipefail # 批量文件备份脚本 BACKUP_SRC( /etc/nginx /etc/mysql /data/app/config ) BACKUP_DEST/data/backup RETENTION_DAYS30 LOG_FILE/var/log/backup.log log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* $LOG_FILE } DATE$(date %Y%m%d_%H%M%S) BACKUP_DIR$BACKUP_DEST/$DATE log 开始备份 mkdir -p $BACKUP_DIR for src in ${BACKUP_SRC[]}; do if [ -e $src ]; then dest_name$(basename $src) log 备份$src - $BACKUP_DIR/$dest_name.tar.gz tar -zcf $BACKUP_DIR/$dest_name.tar.gz -C $(dirname $src) $dest_name else log 警告$src 不存在 fi done # 删除过期备份 log 删除${RETENTION_DAYS}天前的备份 find $BACKUP_DEST -type d -mtime $RETENTION_DAYS -exec rm -rf {} \; log 备份完成脚本 5磁盘空间监控告警#!/bin/bash set -euo pipefail # 磁盘空间监控脚本 # 使用率超过80%告警 THRESHOLD80 LOG_FILE/var/log/disk_monitor.log log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* $LOG_FILE } log 开始磁盘空间检查 df -h | grep -vE Filesystem|tmpfs|loop | while read -r line; do usage$(echo $line | awk {print $5} | cut -d% -f1) mount_point$(echo $line | awk {print $6}) if [ $usage -gt $THRESHOLD ]; then log 警告磁盘 $mount_point 使用率过高${usage}% # 这里可以添加告警逻辑 else log 磁盘 $mount_point 使用率正常${usage}% fi done log 磁盘空间检查完成脚本 6进程和端口检查脚本#!/bin/bash set -euo pipefail # 进程和端口检查脚本 PROCESSES(nginx mysqld dockerd) PORTS(80 443 3306) LOG_FILE/var/log/port_check.log log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* $LOG_FILE } log 开始进程检查 for process in ${PROCESSES[]}; do if pgrep -x $process /dev/null; then log 进程 $process 正在运行 else log 警告进程 $process 未运行 fi done log 开始端口检查 for port in ${PORTS[]}; do if netstat -tlnp | grep -q :$port ; then log 端口 $port 正在监听 else log 警告端口 $port 未监听 fi done log 检查完成脚本 7Docker 容器状态监控#!/bin/bash set -euo pipefail # Docker容器状态监控脚本 LOG_FILE/var/log/docker_monitor.log log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* $LOG_FILE } log 开始Docker容器状态检查 # 检查Docker服务是否运行 if ! systemctl is-active --quiet docker; then log 错误Docker服务未运行 exit 1 fi # 检查所有容器状态 docker ps -a --format {{.Names}}: {{.Status}} | while read -r line; do name$(echo $line | cut -d: -f1) status$(echo $line | cut -d: -f2 | xargs) if [[ $status Up* ]]; then log 容器 $name 运行正常 else log 警告容器 $name 状态异常$status fi done log Docker容器状态检查完成脚本 8K8s 节点健康检查#!/bin/bash set -euo pipefail # K8s节点健康检查脚本 LOG_FILE/var/log/k8s_node_check.log log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* $LOG_FILE } log 开始K8s节点健康检查 # 检查kubectl是否可用 if ! command -v kubectl /dev/null; then log 错误kubectl命令未找到 exit 1 fi # 检查所有节点状态 kubectl get nodes --no-headers | while read -r name status _; do if [ $status Ready ]; then log 节点 $name 状态正常 else log 警告节点 $name 状态异常$status fi done # 检查所有Pod状态 log 检查异常Pod kubectl get pods --all-namespaces --field-selectorstatus.phase!Running,status.phase!Succeeded --no-headers | \ while read -r ns name status _; do log 异常Pod$ns/$name ($status) done log K8s节点健康检查完成脚本 9错误日志分析脚本#!/bin/bash set -euo pipefail # Nginx错误日志分析脚本 NGINX_ERROR_LOG/var/log/nginx/error.log LOG_FILE/var/log/nginx_error_analysis.log REPORT_FILE/tmp/nginx_error_report.txt log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* $LOG_FILE } log 开始Nginx错误日志分析 if [ ! -f $NGINX_ERROR_LOG ]; then log 错误Nginx错误日志文件不存在 exit 1 fi # 统计错误类型 echo Nginx错误日志分析报告 $REPORT_FILE echo 生成时间$(date) $REPORT_FILE echo $REPORT_FILE echo 错误类型统计 $REPORT_FILE awk {print $8} $NGINX_ERROR_LOG | sort | uniq -c | sort -nr | head -10 $REPORT_FILE echo $REPORT_FILE echo 访问最多的IP $REPORT_FILE awk {print $10} $NGINX_ERROR_LOG | grep -oE [0-9]\.[0-9]\.[0-9]\.[0-9] | sort | uniq -c | sort -nr | head -10 $REPORT_FILE log 分析完成报告已保存到$REPORT_FILE脚本 10定时任务管理脚本#!/bin/bash set -euo pipefail # 定时任务管理脚本 CRON_FILE/etc/cron.d/auto_tasks LOG_FILE/var/log/cron_manager.log log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* $LOG_FILE } case ${1:-} in list) echo 当前定时任务 cat $CRON_FILE ;; add) if [ $# -ne 3 ]; then echo 用法$0 add cron表达式 命令 exit 1 fi echo $2 root $3 $CRON_FILE log 添加定时任务$2 $3 echo 定时任务已添加 ;; remove) if [ $# -ne 2 ]; then echo 用法$0 remove 行号 exit 1 fi sed -i ${2}d $CRON_FILE log 删除第$2行定时任务 echo 定时任务已删除 ;; *) echo 用法$0 [list|add|remove] echo list 列出所有定时任务 echo add 添加定时任务 echo remove 删除定时任务 exit 1 ;; esac四、最佳实践总结永远加严格模式set -euo pipefail是你的保命符使用绝对路径避免工作目录变化带来的意外变量加双引号防止空格和特殊字符导致的问题高危命令先判断永远不要直接写rm -rf $dir/*脚本必须有日志出问题时排查全靠它先调试再上线用bash -x逐行检查执行过程保持脚本简洁一个脚本只做一件事添加详细注释方便自己和他人维护五、写在最后Shell 编程是运维工程师的基本功也是你走向自动化运维的第一步。它不需要你有多高深的编程能力但需要你有严谨的态度和良好的习惯。希望这篇文章能帮助你写出更规范、更优雅、更安全的 Shell 脚本。如果你觉得有用欢迎分享给你的同事和朋友。

更多文章