每个经常和Linux打交道的运维或开发人员,大概都有过这样的经历:每天上班第一件事就是手动登录服务器,敲一遍几乎相同的命令,查看磁盘占用、清理过期日志、备份数据库。这些操作本身不难,但日复一日地重复,既浪费时间又容易出错。Shell脚本的价值就在这里:把一串命令固化成文件,交给系统定时执行,人只需要在出问题时看一眼结果。本文将从基础语法讲到实战案例,带你完整走一遍用Shell脚本自动化日常任务的过程。

一、Shell脚本的基本结构
Shell脚本本质上就是一个文本文件,里面按顺序存放着你平时手动输入的命令。第一行通常是shebang,也就是#!/bin/bash,它告诉系统用哪个解释器来执行这个文件。如果省略这一行,脚本会由当前默认的Shell解释,某些语法在bash和sh之间并不兼容,显式声明解释器是个好习惯。
一个规范的脚本还应该包含注释和退出码。注释用#开头,给自己或后人留说明;脚本结束时用exit 0表示成功,非零值表示失败。很多调度系统正是依赖退出码来判断任务是否跑成功,这一点经常被新手忽略。
#!/bin/bash
# 这是一个演示脚本:显示当前时间和磁盘占用
echo "脚本开始执行:$(date '+%Y-%m-%d %H:%M:%S')"
df -h | grep -v tmpfs
if [ $? -eq 0 ]; then
echo "磁盘检查完成"
exit 0
else
echo "磁盘检查异常"
exit 1
fi
执行脚本有两种方式:一是先chmod +x script.sh赋予执行权限,再直接运行./script.sh;二是用bash script.sh显式指定解释器,这种方式不需要执行权限,调试时更方便。建议在开发阶段用第二种,上线后再加权限。
二、变量、条件与循环:脚本的三大支柱
变量是Shell脚本里最基础的部分。定义变量时等号两边不能有空格,这是初学者最容易踩的坑:name="value"是对的,name = "value"会被解释成执行一个叫name的命令。引用变量时用$name或者花括号形式${name},当变量后面紧跟其他字符时,花括号是必须的,否则会产生歧义。
条件判断使用if语句,配合test命令或双方括号。判断文件是否存在、字符串是否相等、数值大小,都有对应的运算符。注意字符串比较用=,数值比较则要用-eq、-lt这类写法,混用会得到奇怪的结果。循环方面,for适合遍历已知列表,while适合按行读取文件或无限轮询的场景。
#!/bin/bash
# 遍历目录中的日志文件,超过100M的输出警告
LOG_DIR="/var/log/myapp"
for file in "$LOG_DIR"/*.log; do
if [ ! -f "$file" ]; then
continue
fi
size=$(du -m "$file" | awk '{print $1}')
if [ "$size" -gt 100 ]; then
echo "警告:$file 大小已达到 ${size}MB"
fi
done
函数同样重要。把重复出现的逻辑封装成函数,能让脚本结构清晰许多。函数内的变量默认是全局的,如果需要局部变量,记得加local关键字,否则很容易在不知不觉中污染外层变量,排查起来非常痛苦。
三、三个可以直接落地的自动化案例
案例一:网站目录定时备份
备份是最常见的自动化需求。下面的脚本把网站目录打包压缩,文件名带上日期戳,保留最近七天的备份,更早的自动删除。用tar的-z选项做gzip压缩,对于纯文本为主的网站目录,压缩比相当可观。
#!/bin/bash
# 网站目录每日备份脚本
WEB_DIR="/var/www/html"
BACKUP_DIR="/data/backup"
DATE=$(date '+%Y%m%d')
KEEP_DAYS=7
mkdir -p "$BACKUP_DIR"
tar -czf "$BACKUP_DIR/web_${DATE}.tar.gz" "$WEB_DIR"
# 删除过期备份
find "$BACKUP_DIR" -name "web_*.tar.gz" -mtime +$((KEEP_DAYS - 1)) -delete
echo "备份完成:web_${DATE}.tar.gz"
案例二:日志轮转与清理
应用日志如果不管理,迟早会把磁盘撑爆。虽然系统自带的logrotate能处理大部分场景,但自己写脚本可以做更灵活的判断,比如按大小切割、异常关键字告警。这个脚本检查指定日志的大小,超过阈值就重命名归档并通知新文件从头开始写。
#!/bin/bash
# 日志过大时切割归档
LOG_FILE="/var/log/myapp/app.log"
MAX_SIZE=$((100 * 1024 * 1024)) # 100MB
if [ ! -f "$LOG_FILE" ]; then
echo "日志文件不存在" >&2
exit 1
fi
actual_size=$(stat -c%s "$LOG_FILE")
if [ "$actual_size" -gt "$MAX_SIZE" ]; then
mv "$LOG_FILE" "${LOG_FILE}.$(date '+%Y%m%d%H%M%S')"
# 发送HUP信号让应用重新打开日志文件
kill -HUP $(pgrep -f myapp) 2>/dev/null
echo "日志已切割"
fi
案例三:批量文件重命名
把一批照片按拍摄日期重命名,或者把下载的资源统一改扩展名,这类需求手动做非常折磨人。借助循环加字符串处理,几行代码就能完成。下面的脚本把当前目录下所有大写的JPG扩展名统一改成小写。
#!/bin/bash
# 批量将 JPG 改为 jpg
count=0
for f in *.JPG; do
[ -e "$f" ] || break
mv "$f" "${f%.JPG}.jpg"
count=$((count + 1))
done
echo "共处理 ${count} 个文件"
其中${f%.JPG}是Shell的参数展开语法,表示去掉变量值末尾匹配的字符串,类似的还有${f#*.}去掉开头匹配部分,掌握这些写法能省去很多调用外部命令的开销。
四、用crontab让脚本定时执行
脚本写好了,还需要让它自动运行,这就轮到crontab登场。执行crontab -e编辑当前用户的任务列表,每一行代表一个任务,格式为五个时间字段加要执行的命令,依次是分钟、小时、日、月、星期。比如每天凌晨两点半执行备份脚本,写成30 2 * * * /data/scripts/backup.sh >> /var/log/backup.log 2>&1。
几个实用细节值得注意。第一,cron环境变量极简,PATH往往只有/usr/bin:/bin,脚本里如果调用了非标准路径的程序,最好在开头显式export PATH。第二,输出重定向很重要,把标准输出和错误都追加到日志文件,出问题才有迹可循。第三,用crontab -l可以查看已配置的任务,排查时先确认任务确实存在。
调试脚本时,推荐开启bash的调试模式:bash -x script.sh会把每一行执行前的展开结果打印出来,定位变量取值问题特别有效。也可以在脚本内用set -e让任何命令失败时立即退出,用set -u引用未定义变量时报错,这两个选项能拦住大量隐蔽的坑。
把重复劳动交给脚本是提升效率最直接的方式。从今天开始,每当你发现自己在重复敲同一串命令,就把它记下来攒成脚本,配合crontab逐步搭建属于自己的自动化体系,一个月后回头再看,会明显感觉到工作方式的变化。
Shell脚本自动化crontab定时任务修改时间:2026-09-07 20:50:42