嘿,朋友。既然你点开了这篇内容,我猜你现在正对着那个闪烁的光标发呆,或者已经被繁琐的重复劳动折磨得够呛。别担心,我也曾是从“rm -rf / 都手抖”的新手,一路摸爬滚打到现在。Shell 不仅仅是敲命令,它是你在这个数字世界里最顺手的一把瑞士军刀。今天我不跟你扯什么“Shell的历史”、“POSIX标准”这些干巴巴的教科书内容,咱们直接上干货。我会带你把这30个真正在运维一线被验证过、能救命、能提速的实战案例啃下来。
准备好了吗?咱们开始。
第一章:热身与基础——别让低级错误毁了你
在写大脚本之前,我们先从最接地气的地方入手。很多老鸟容易忽视这部分,但恰恰是这些细节决定了脚本的健壮性。
1.1 第一个脚本:你好,世界(但要优雅一点)
很多人写脚本直接 #!/bin/bash 开始,太粗糙了。咱们得有点专业范儿。
#!/bin/bash
#
# 文件名: hello.sh
# 描述: 标准的Hello World,但展示了良好的注释规范
# 作者: 你的邻居老鸟
# 日期: $(date +%Y-%m-%d)
set -e # 遇到错误立即退出,这是好习惯!
set -u # 未定义变量时报错,防止空变量引发的灾难
set -o pipefail # 管道中任何一步出错都返回错误码
echo "你好,Linux世界!"
echo "当前时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "当前用户: $(whoami)"
为什么这么做?
set -e是救命稻草。想象一下,你写了个备份脚本,中间某步失败了,但脚本还在继续往下跑,最后你发现备份是空的,那就是没加这个参数。- 注释里写
$(date ...)让脚本能自我更新日期,不用每次手动改。
1.2 变量与引用:双引号是保护神
新手常犯的错误:name=John; echo $name 没问题,但如果 name="John Doe" 呢?直接写 echo $name 会报错或者只显示第一个字。
name="John Doe"
# 错误示范:如果变量名后面紧跟字母,会混淆
echo $name1 # 这会去找名为 name1 的变量,而不是 name 后面跟个1
# 正确示范:永远用双引号包裹变量
echo "$name" # 输出: John Doe
echo "${name}Smith" # 输出: John DoeSmith
# 命令替换
current_dir=$(pwd)
echo "当前目录是: $current_dir"
老鸟心得:养成习惯,凡是涉及变量的地方,哪怕只有一个字,也加上双引号。这能避免80%的莫名其妙错误。
1.3 参数传递:让脚本更灵活
#!/bin/bash
# 用法: ./greet.sh [名字] [年龄]
# 检查参数数量
if [ $# -lt 1 ]; then
echo "用法: $0 <名字> [年龄]"
exit 1
fi
name=$1
age=${2:-"未知"} # 如果第二个参数没给,默认为"未知"
echo "你好, $name! 你今年 $age 岁了。"
# 遍历所有参数
echo "你传递的所有参数:"
for param in "$@"; do
echo " - $param"
done
第二章:流程控制——让脚本学会思考
脚本不能只会线性执行,它得会判断、会循环、会做选择。
2.1 条件判断:if/else 的正确姿势
#!/bin/bash
# 检查文件是否存在并判断类型
target_file="/tmp/test.log"
if [ -f "$target_file" ]; then
echo "✅ 文件存在: $target_file"
elif [ -d "$target_file" ]; then
echo "❌ 这是一个目录,不是文件: $target_file"
else
echo "⚠️ 文件不存在,即将创建: $target_file"
touch "$target_file"
fi
# 数字比较 vs 字符串比较
num=10
if [ "$num" -eq 10 ]; then
echo "数字相等"
fi
str="hello"
if [ "$str" = "hello" ]; then
echo "字符串相等"
fi
关键点:
-f检查普通文件,-d检查目录,-e检查是否存在(不管类型)。-eq是数字等于,=是字符串等于。混用会出大乱子。
2.2 循环结构:for 与 while 的战场
#!/bin/bash
# 1. for 循环:处理文件列表
echo "=== 当前目录下的 .txt 文件 ==="
for file in *.txt; do
[ -e "$file" ] || continue # 如果没有txt文件,跳过
echo "处理文件: $file"
# 假装做点事情
done
# 2. while 循环:持续监控
echo "=== 监控 CPU 使用率 (按 Ctrl+C 停止) ==="
count=0
while [ $count -lt 5 ]; do
# 获取CPU使用率
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
echo "第 $((count+1)) 次监控: CPU $cpu_usage%"
sleep 2
count=$((count + 1))
done
# 3. until 循环:直到满足条件
echo "=== 等待服务启动 ==="
until netstat -tlnp 2>/dev/null | grep -q ":8080"; do
echo "服务尚未启动,等待5秒..."
sleep 5
done
echo "✅ 8080端口服务已启动!"
2.3 case 语句:多选一的优雅
当你需要处理多个离散选项时,if/elif/else 会变得臃肿,case 是更好的选择。
#!/bin/bash
# 一个简单的状态管理脚本
action=$1
case "$action" in
start)
echo "🚀 启动服务..."
# 执行启动命令
;;
stop)
echo "🛑 停止服务..."
# 执行停止命令
;;
restart)
echo "🔄 重启服务..."
"$0" stop
sleep 2
"$0" start
;;
status)
echo "📊 检查服务状态..."
# 执行状态检查
;;
*)
echo "❌ 未知操作: $action"
echo "可用操作: start, stop, restart, status"
exit 1
;;
esac
第三章:文本处理三剑客——运维者的核武器
在Linux世界里,grep, sed, awk 是你的三剑客。不懂它们,你连日志都看不懂。
3.1 Grep:在数据海洋中捞针
# 基础用法
grep "error" /var/log/syslog
# 常用参数组合
grep -i "error" /var/log/syslog # -i 忽略大小写
grep -n "error" /var/log/syslog # -n 显示行号
grep -c "error" /var/log/syslog # -c 统计匹配行数
grep -r "error" /etc/nginx/ # -r 递归搜索目录
grep -v "debug" /var/log/app.log # -v 反向选择,排除debug行
grep -E "error|fatal|critical" /var/log/syslog # -E 扩展正则,匹配多个关键词
# 实战:查找被锁定的用户
grep "nologin" /etc/passwd | cut -d: -f1
技巧:grep -A 5 (After) 和 grep -B 5 (Before) 可以显示匹配行前后的内容,排查日志时极有用。
3.2 Sed:文本的外科手术
# 替换文本
sed 's/old_text/new_text/g' file.txt # 全局替换
sed 's/^[0-9]*//' file.txt # 删除行首的数字
# 删除特定行
sed '3d' file.txt # 删除第3行
sed '/error/d' file.txt # 删除包含error的行
# 插入和附加
sed '3i 新内容' file.txt # 在第3行前插入
sed '3a 新内容' file.txt # 在第3行后插入
# 实战:备份并修改配置文件
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
sed -i 's/listen 80/listen 8080/g' /etc/nginx/nginx.conf
echo "✅ Nginx监听端口已修改为8080"
注意:-i 参数是直接修改原文件,使用前务必确认!建议先备份。
3.3 Awk:数据分析师的首选
# 基础:打印指定列
awk '{print $1, $3}' access.log
# 实战:统计Nginx日志中每个IP的访问次数
awk '{print $1}' access.log | sort | uniq -c | sort -nr | head -10
# 更复杂的Awk用法
awk 'BEGIN {count=0} $9 ~ /404/ {count++} END {print "404错误总数:", count}' access.log
# 实战:监控系统负载
uptime | awk -F'load average:' '{print "当前系统负载:", $2}'
# 实战:从JSON-like日志中提取字段 (假设格式为 key=value)
echo "user=admin status=200 ip=192.168.1.1" | awk -F'[ =]' '{for(i=1;i<=NF;i+=2) printf "%s -> %s\n", $i, $(i+1)}'
老鸟心得:Awk的BEGIN和END块让你能在处理数据前后执行初始化或汇总操作,这是它比sed强大的地方。
第四章:文件与目录操作——自动化管理的基石
4.1 批量重命名:告别手动点击
#!/bin/bash
# 批量将文件名中的空格替换为下划线
echo "=== 开始批量重命名 ==="
for file in *; do
if [[ -f "$file" ]]; then
new_name="${file// /_}" # 替换空格为下划线
if [ "$file" != "$new_name" ]; then
mv "$file" "$new_name"
echo "✅ $file -> $new_name"
fi
fi
done
echo "=== 完成 ==="
4.2 查找并清理旧文件
#!/bin/bash
# 清理7天前的日志文件
log_dir="/var/log/myapp"
days=7
echo "正在清理 $log_dir 中超过 $days 天的日志..."
# 查找并删除
find "$log_dir" -name "*.log" -type f -mtime +$days -delete
# 统计清理前后的空间
before=$(du -sh "$log_dir" | cut -f1)
find "$log_dir" -name "*.log" -type f -mtime +$days -exec rm {} \;
after=$(du -sh "$log_dir" | cut -f1)
echo "清理前: $before"
echo "清理后: $after"
echo "✅ 清理完成"
4.3 权限批量修改
#!/bin/bash
# 设置目录权限为755,文件权限为644
target_dir="$1"
if [ -z "$target_dir" ]; then
echo "用法: $0 <目录路径>"
exit 1
fi
echo "正在修改 $target_dir 的权限..."
# 目录设为755
find "$target_dir" -type d -exec chmod 755 {} \;
echo "✅ 目录权限设为755"
# 文件设为644
find "$target_dir" -type f -exec chmod 644 {} \;
echo "✅ 文件权限设为644"
第五章:进程与系统管理——掌控服务器命脉
5.1 进程监控与自动重启
#!/bin/bash
# 监控nginx进程,如果不存在则重启
service_name="nginx"
if ! pgrep -x "$service_name" > /dev/null; then
echo "⚠️ $service_name 未运行,正在尝试重启..."
systemctl restart "$service_name"
sleep 2
if pgrep -x "$service_name" > /dev/null; then
echo "✅ $service_name 重启成功"
else
echo "❌ $service_name 重启失败,请手动检查"
exit 1
fi
else
echo "✅ $service_name 运行正常 (PID: $(pgrep -x "$service_name" | head -1))"
fi
5.2 资源监控脚本
#!/bin/bash
# 服务器资源监控报告
echo "========================================"
echo " 服务器资源监控报告"
echo " 时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "========================================"
# CPU负载
load=$(uptime | awk -F'load average:' '{print $2}' | xargs)
echo "📊 系统负载: $load"
# 内存使用
echo ""
echo "💾 内存使用情况:"
free -h | awk '/^Mem:/ {printf " 已用: %s / 总计: %s (%.1f%%)\n", $3, $2, $3/$2*100}'
# 磁盘使用
echo ""
echo "💽 磁盘使用率 (Top 5):"
df -h | grep -E '^/dev/' | head -5 | awk '{printf " %s: 已用 %s / %s (%s)\n", $1, $3, $2, $5}'
# 僵尸进程检查
zombie_count=$(ps aux | grep -E '^[^ ]+ [^ ]+ [^ ]+ [^ ]+ Z' | wc -l)
if [ $zombie_count -gt 0 ]; then
echo "⚠️ 检测到 $zombie_count 个僵尸进程!"
else
echo "✅ 无僵尸进程"
fi
echo "========================================"
第六章:网络自动化——让连接更智能
6.1 端口扫描与连通性测试
#!/bin/bash
# 批量检查主机端口连通性
hosts=("192.168.1.1" "192.168.1.2" "192.168.1.3")
ports=(22 80 443 3306 8080)
for host in "${hosts[@]}"; do
echo "========================================"
echo "主机: $host"
echo "========================================"
for port in "${ports[@]}"; do
# 使用timeout避免卡住
if timeout 2 bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null; then
echo " ✅ 端口 $port: 开放"
else
echo " ❌ 端口 $port: 关闭/过滤"
fi
done
done
6.2 HTTP状态码检查
#!/bin/bash
# 检查多个URL的HTTP状态码
urls=(
"http://example.com"
"https://google.com"
"http://localhost:8080"
)
for url in "${urls[@]}"; do
# 获取HTTP状态码
status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url")
case $status in
200) echo "✅ $url: $status (OK)" ;;
301|302) echo "⏭️ $url: $status (重定向)" ;;
404) echo "❌ $url: $status (未找到)" ;;
500) echo "💥 $url: $status (服务器错误)" ;;
*) echo "❓ $url: $status" ;;
esac
done
第七章:备份与恢复——数据安全的最后防线
7.1 数据库备份脚本
”`bash #!/bin/bash
MySQL数据库自动备份
DB_USER=“backup_user” DB_PASS=“your_password” DB_NAME=“my_database” BACKUPDIR=“/backup/mysql” DATE=$(date +%Y%m%d%H%M%S) BACKUP_FILE=”\(BACKUP_DIR/\){DBNAME}${DATE}.sql.gz”
创建备份目录(如果不存在)
mkdir -p “$BACKUP_DIR”
echo “开始备份数据库: $DB_NAME”
执行备份并压缩
mysqldump -u”\(DB_USER" -p"\)DB_PASS” “\(DB_NAME" | gzip > "\)BACKUP_FILE”
if [ $? -eq 0 ]; then
echo "✅ 备份成功: $BACKUP_FILE"
echo "📦 文件大小: $(du -h "$BACKUP_FILE" | cut -f
