为什么运维要学 Shell 脚本?
运维工作里有一个"三次原则":同一个操作如果你手动做了三次,就应该把它写成脚本。Shell 脚本是 Linux 运维最基本的自动化手段——不需要额外安装任何软件,每台服务器原生支持,学会了从此告别重复劳动。
一、第一个脚本
1.1 创建脚本文件
#!/bin/bash
# 我的第一个运维脚本
echo "=== 服务器基本信息 ==="
echo "主机名:$(hostname)"
echo "系统版本:$(cat /etc/os-release | head -1)"
echo "运行时间:$(uptime -p)"
echo "当前内存:$(free -h | awk '/^Mem/ {print $3 "/" $2}')"
echo "磁盘用量:$(df -h / | awk 'NR==2 {print $3 "/" $2}')"# 保存为 server-info.sh,然后执行
chmod +x server-info.sh
./server-info.sh#!/bin/bash 是 shebang,告诉系统用哪个解释器来运行这个脚本。写脚本第一步,先想清楚这个脚本能帮你省多少事。
1.2 变量与系统信息采集
#!/bin/bash
# 服务器巡检脚本
# 定义变量
DATE=$(date +%F_%H:%M)
LOG_DIR="/var/log/audit"
THRESHOLD_DISK=80 # 磁盘阈值 %
THRESHOLD_MEM=90 # 内存阈值 %
# 创建日志目录
mkdir -p "$LOG_DIR"
# 系统信息采集函数
get_system_info() {
echo "=== 巡检时间: $DATE ==="
echo "CPU 负载: $(uptime | awk -F'load average:' '{print $2}')"
echo "内存使用: $(free -m | awk '/Mem:/ {printf "%.1f%%", $3/$2 * 100}')"
echo "磁盘使用: $(df -h / | awk 'NR==2 {print $5}')"
echo "连接数: $(ss -tun | wc -l)"
}
# 调用函数
get_system_info >> "$LOG_DIR/server-check-$(date +%F).log"变量使用要点:
VAR=value赋值,等号两边不能有空格$VAR或${VAR}引用变量$(command)获取命令输出- 引用变量时养成加双引号的习惯,避免空格和特殊字符出问题
二、条件判断——让脚本变聪明
2.1 if 语句
#!/bin/bash
# 磁盘告警脚本
DISK_USED=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$DISK_USED" -gt 90 ]; then
echo "⚠️ 磁盘使用率 ${DISK_USED}%,告警!"
# 发送告警(示例:写入日志)
echo "$(date): DISK WARNING at ${DISK_USED}%" >> /var/log/disk-alert.log
elif [ "$DISK_USED" -gt 75 ]; then
echo "⚠️ 磁盘使用率 ${DISK_USED}%,需要注意"
else
echo "✅ 磁盘使用率 ${DISK_USED}%,正常"
fi常用判断操作符:
| 操作符 | 说明 | 示例 |
|---|---|---|
-gt | 大于 | [ "$a" -gt 10 ] |
-lt | 小于 | [ "$a" -lt 10 ] |
-eq | 等于 | [ "$a" -eq 10 ] |
-z | 字符串为空 | [ -z "$var" ] |
-n | 字符串非空 | [ -n "$var" ] |
-f | 文件存在 | [ -f /etc/nginx.conf ] |
-d | 目录存在 | [ -d /var/log ] |
2.2 实战:检查服务是否在运行
#!/bin/bash
# 服务健康检查脚本
SERVICES=("nginx" "mysqld" "php-fpm")
for svc in "${SERVICES[@]}"; do
if systemctl is-active --quiet "$svc"; then
echo "✅ $svc 运行正常"
else
echo "❌ $svc 未运行,尝试重启..."
systemctl restart "$svc"
if [ $? -eq 0 ]; then
echo " ✅ $svc 已重启成功"
echo "$(date): $svc was down, restarted" >> /var/log/service-recovery.log
else
echo " ❌ $svc 重启失败,请手动排查"
fi
fi
done三、循环——批量操作的利器
3.1 for 循环
#!/bin/bash
# 批量检查多台服务器
SERVERS=("web01" "web02" "db01" "cache01")
for host in "${SERVERS[@]}"; do
echo "正在检查 $host ..."
ssh "$host" "uptime && df -h / | tail -1" 2>/dev/null
if [ $? -ne 0 ]; then
echo " ❌ $host 连接失败"
fi
echo "---"
done3.2 实战:批量清理30天前的日志
#!/bin/bash
# 批量日志清理脚本
LOG_PATHS=(
"/var/log/nginx"
"/var/log/mysql"
"/var/log/php"
"/opt/app/logs"
)
for path in "${LOG_PATHS[@]}"; do
if [ -d "$path" ]; then
count=$(find "$path" -name "*.log" -mtime +30 | wc -l)
if [ "$count" -gt 0 ]; then
echo "清理 $path: 发现 $count 个30天前日志文件"
find "$path" -name "*.log" -mtime +30 -exec rm -f {} \;
echo " ✅ 清理完成,释放空间: $(du -sh "$path" | awk '{print $1}')"
else
echo "✅ $path 无需清理"
fi
else
echo "⚠️ $path 目录不存在,跳过"
fi
done四、运维脚本最佳实践
4.1 脚本模板
一个合格的运维脚本,应该包含以下要素:
#!/bin/bash
#=============================================
# 脚本名称: example.sh
# 功能描述: 一句话说明这个脚本干什么
# 使用方式: ./example.sh [参数1] [参数2]
# 作者: 运维工程师
# 创建日期: 2024-01-01
# 最后修改: 2026-07-29
#=============================================
set -euo pipefail # 遇到错误立即退出,变量未定义报错
# 常量定义
readonly SCRIPT_NAME=$(basename "$0")
readonly VERSION="1.0.0"
readonly LOG_FILE="/var/log/scripts/${SCRIPT_NAME%.sh}.log"
# 日志函数
log_info() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*" | tee -a "$LOG_FILE"
}
log_error() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*" | tee -a "$LOG_FILE" >&2
}
# 使用说明
usage() {
cat << EOF
用法: $SCRIPT_NAME [选项]
选项:
-h 显示帮助
-v 显示版本
-m MODE 运行模式 (check|clean|report)
示例:
$SCRIPT_NAME -m check # 执行检查
$SCRIPT_NAME -m clean # 执行清理
$SCRIPT_NAME -m report # 生成报告
EOF
exit 0
}
# 主逻辑
main() {
log_info "脚本开始执行,参数: $*"
# 在这里写你的核心逻辑
log_info "脚本执行完成"
}
# 参数解析
while getopts "hvm:" opt; do
case $opt in
h) usage ;;
v) echo "$SCRIPT_NAME v$VERSION"; exit 0 ;;
m) MODE="$OPTARG" ;;
*) usage ;;
esac
done
# 执行入口
main "$@"4.2 常用技巧总结
1. set -euo pipefail
写脚本时强烈建议加上这行,它会让脚本在遇到任何错误时立即退出,避免错误被忽略导致更严重的问题。
2. 日志记录
所有的操作都要有日志,不然出了问题你都不知道脚本什么时候跑过。
3. 返回值检查
if ! command_that_might_fail; then
log_error "命令执行失败"
exit 1
fi4. 临时文件管理
TMPFILE=$(mktemp)
trap "rm -f $TMPFILE" EXIT # 脚本退出时自动清理5. 幂等性
同一个脚本重复执行多次,结果应该是一样的。比如"清理30天前的日志"重复跑也不会误删当天的日志。
6. 给脚本加锁防止并发
LOCKFILE="/var/lock/$(basename $0).lock"
exec 200>"$LOCKFILE"
flock -n 200 || { echo "脚本已在运行"; exit 1; }五、三个可以直接用的脚本
5.1 服务器巡检脚本
#!/bin/bash
# daily-check.sh — 每天早上8点发送服务器状态报告
REPORT="/tmp/server-report-$(date +%F).txt"
{
echo "======================================"
echo " 服务器巡检报告 - $(date +%F)"
echo " 主机: $(hostname)"
echo "======================================"
echo ""
echo "【系统负载】"
uptime
echo ""
echo "【CPU TOP 5 进程】"
ps aux --sort=-%cpu | head -6
echo ""
echo "【内存 TOP 5 进程】"
ps aux --sort=-%mem | head -6
echo ""
echo "【磁盘使用情况】"
df -h | grep -v tmpfs
echo ""
echo "【网络连接数】"
echo "ESTABLISHED: $(ss -tun state established | wc -l)"
echo "TIME_WAIT: $(ss -tun state time-wait | wc -l)"
echo ""
echo "【监听端口】"
ss -tnlp
} > "$REPORT"
# 可将报告通过邮件或企业微信发送
cat "$REPORT"5.2 网站批量部署脚本
#!/bin/bash
# deploy.sh — 从 Git 拉取代码并部署
set -euo pipefail
APP_DIR="/var/www/myapp"
BACKUP_DIR="/backup/app"
DATE=$(date +%Y%m%d_%H%M%S)
# 1. 备份当前版本
echo ">>> 备份当前版本..."
mkdir -p "$BACKUP_DIR"
tar -czf "$BACKUP_DIR/myapp-$DATE.tar.gz" -C "$APP_DIR" .
# 2. 拉取最新代码
echo ">>> 拉取最新代码..."
cd "$APP_DIR"
git pull origin main
# 3. 安装依赖
echo ">>> 安装依赖..."
composer install --no-dev --optimize-autoloader
# 4. 执行数据库迁移
echo ">>> 执行数据库迁移..."
php artisan migrate --force
# 5. 清理缓存
echo ">>> 清理缓存..."
php artisan optimize
# 6. 设置权限
echo ">>> 设置目录权限..."
chown -R www-data:www-data "$APP_DIR"
chmod -R 755 "$APP_DIR/storage"
# 7. 重启服务
echo ">>> 重启 PHP-FPM..."
systemctl reload php8.1-fpm
echo "✅ 部署完成!保留备份: myapp-$DATE.tar.gz"5.3 日志轮转手动触发脚本
#!/bin/bash
# force-logrotate.sh — 对指定服务的日志进行轮转
if [ $# -eq 0 ]; then
echo "用法: $0 <服务名>"
echo "示例: $0 nginx"
exit 1
fi
SERVICE=$1
CONF_FILE="/etc/logrotate.d/$SERVICE"
if [ ! -f "$CONF_FILE" ]; then
echo "❌ 未找到 $SERVICE 的 logrotate 配置"
exit 1
fi
echo ">>> 正在轮转 $SERVICE 日志..."
logrotate -vf "$CONF_FILE"
if [ $? -eq 0 ]; then
echo "✅ 日志轮转成功"
else
echo "❌ 日志轮转失败,检查配置"
fi总结
Shell 脚本是运维自动化的基础工具,学会它之后:
- 重复性工作交给脚本,自己可以干更有价值的事
- 操作的流程被固化下来,不会出现"这次忘了步骤"的情况
- 脚本可以轻松在几十台服务器上批量执行
- 一份好的脚本,换个新人也能正确操作
记住三个原则:
- 手动操作三次就写成脚本
- 脚本必须有日志和错误处理
- 核心脚本纳入版本管理(Git)
评论