🖥️ 第09章 · Shell 脚本入门
变量/条件/循环/函数/定时
Shell 脚本入门¶
这一章把前面所有命令串成一门"小语言":变量、条件、循环、函数,再到这段落进定时任务里无人值守地运行。学完你就能写出像 /root/scripts/tieba_sign.py、monitor_report.py 那样的自动化脚本。
什么是 Shell 脚本¶
Shell 脚本就是一个按顺序存放命令的文本文件,用解释器(通常是 bash)逐行执行。它适合"把一组日常命令固化下来",比如隔天跑一次备份、每晚发一次监控报告。
一个最简脚本:
#!/bin/bash
# 第一行 shebang,告诉系统用哪个解释器执行
echo "hello, welcome to $HOSTNAME"
whoami
date
提示
#!/bin/bash是脚本第一行,不可省。它决定了用 bash 解释还是其它;写错解释器会得到一堆奇怪的语法错误。
创建并运行脚本¶
# 用任意编辑器写好,保存为 script.sh
chmod +x script.sh # 加执行权限(第 4 章)
./script.sh # 方式1:直接执行
bash script.sh # 方式2:用解释器执行(无需 x 权限)
注意
./script.sh需要执行权限;bash script.sh把文件当成参数交给 bash,不需要执行位。两者都能跑,但./必须chmod +x。
变量¶
Shell 变量不加类型,赋值时 = 两边不能有空格。
name="srv1952823"
echo "这是 $name" # 用 $ 或 ${} 取值
echo "脚本名是 $0" # $0 是脚本自身
echo "第1个参数是 $1" # $1 第1个参数,$2 第2个...
echo "参数个数 $#"
echo "全部参数 $*"
| 特殊变量 | 含义 |
|---|---|
$0 |
脚本名 |
$1~$9 |
第 1~9 个参数 |
$# |
参数个数 |
$* / $@ |
所有参数(整体 / 逐个) |
$? |
上一条命令的退出码(0 表示成功) |
$$ |
当前脚本的 PID |
# 经典用法:给变量附上命令结果
today=$(date +%F) # 命令替换,把 date 的输出赋给变量
echo "今天是 $today"
提示 取值用
$,赋值不带$且等号两侧无空格。$(命令)与反引号`命令`等价,推荐用$(...),可嵌套、可读性更好。
条件判断 if¶
if [ 条件 ]; then
命令
elif [ 另一个条件 ]; then
命令
else
命令
fi
# 判断目录是否存在
if [ -d /var/www/bangdream ]; then
echo "目录存在"
else
echo "目录不存在"
fi
常用条件运算符:
| 写法 | 含义 |
|---|---|
[ -f 文件 ] |
文件存在且是普通文件 |
[ -d 目录 ] |
目录存在 |
[ -e 路径 ] |
路径存在(文件或目录) |
[ "$a" = "$b" ] |
字符串相等(注意有空格) |
[ "$a" -gt 5 ] |
数字大于 5(大于用 -gt) |
[ -z "$a" ] |
字符串为空 |
[ -n "$a" ] |
字符串非空 |
注意
[前后与=、-gt两侧都要有空格——[$a=$b]是错的。也可以用test 条件,与[ ]等价。[ ]用于字符串/文件,(( ))用于数字运算更直观。
循环 for 与 while¶
for 变量 in 值列表; do
命令
done
# 遍历一组文件名
for f in /var/www/bangdream/*.log; do
echo "处理 $f"
done
# 数字循环
for i in {1..5}; do
echo "第 $i 次"
done
while [ 条件 ]; do
命令
done
# 每 60 秒执行一次(配合 sleep)
while :; do
uptime >> /tmp/uptime.log
sleep 60
done
提示 写循环前先手动跑一次命令确认结果,再放循环里。循环体里常配
sleep,给系统留点喘息,避免 CPU 打满。
函数¶
把可复用逻辑封装成函数:
函数名() {
命令
命令
}
函数名 # 调用
backup() {
tar -czf /root/backup/www-$(date +%F).tar.gz -C /var/www bangdream
echo "已备份到 $?"
}
backup # 调用
案例:定时备份脚本¶
把第 5、6 章的内容组合成一个可运行脚本:
#!/bin/bash
# 每周/每日备份 bangdream 应用目录到 /root/backup
BACKUP_DIR="/root/backup"
SRC="/var/www/bangdream"
DATE=$(date +%F)
DEST="$BACKUP_DIR/bangdream-$DATE.tar.gz"
if [ ! -d "$BACKUP_DIR" ]; then
mkdir -p "$BACKUP_DIR"
fi
tar -czf "$DEST" -C /var/www bangdream 2>/dev/null
if [ $? -eq 0 ]; then
echo "[ok] 备份完成: $DEST"
else
echo "[fail] 备份失败: $DEST" >&2
exit 1
fi
| 要点 | 解释 |
|---|---|
if [ ! -d ... ] |
目录不存在则先创建 |
2>/dev/null |
丢弃 tar 的进度噪声 |
$? |
检查 tar 是否成功 |
exit 1 |
失败时以非零码退出,便于自动化识别 |
| 带日期文件名 | 不会覆盖旧备份(第 5 章) |
定时任务与 cron¶
脚本写完,经常要定时自动跑,而不是手动执行。cron 是最常用的方案。
crontab -e # 编辑当前用户的定时任务表
crontab -l # 查看当前用户的定时任务
cron 时间格式:五段,分别为 分 时 日 月 周。
分钟(0-59) 小时(0-23) 日(1-31) 月(1-12) 周(0-7)
# 每天 7:00 运行贴吧签到
0 7 * * * /usr/bin/python3 /root/scripts/tieba_sign.py >> /var/log/tieba_sign.log 2>&1
# 每天 8:00 运行监控报告
0 8 * * * /usr/bin/python3 /root/scripts/monitor_report.py >> /var/log/monitor_report.log 2>&1
注意 这两行正好对应这台服务器上的真实 cron 任务。cron 里务必用绝对路径、加上
>> 日志 2>&1,否则看不到运行结果;cron 默认环境极简,PATH可能不含/usr/local/bin,所以在 cron 里调用命令要么写绝对路径,要么在脚本开头export PATH=...。
| 时间写法 | 含义 |
|---|---|
7 * * * * |
每小时的第 07 分 |
0 8 * * * |
每天 08:00 |
0 3 * * * |
每天 03:00(适合压点备份) |
30 2 * * 6 |
每周六 02:30 |
脚本调试与规范¶
| 惯用法 | 作用 |
|---|---|
set -e |
任何命令失败就立即退出脚本 |
set -u |
用未定义变量时报错 |
bash -x script.sh |
逐条打印执行过程(排错神器) |
| 全大写的常量名 | 如 BACKUP_DIR,一眼看出是常量 |
关键步骤加 echo |
输出中间状态,便于跟踪 |
提示 脚本第一句话常写
#!/bin/bash和set -e。遇到"脚本看起来没跑成功"就加bash -x,看它卡在哪一行。
易错点¶
- 缺
chmod +x:不用bash直接./会报 permission denied。 =两侧有空格:shell 会当命令解析而报错。[ ]里忘了空格:简直是最隐蔽的坑。- cron 里用相对路径:环境极简,一定要绝对路径。
- 脚本没加
&或;分离命令:echo a echo b会被当两个命令,除非换行或加;。
相关命令¶
#!/bin/bash(shebang)、set -e/-u/-x、if、for、while、case、函数、crontab、at(一次性定时)、systemd timer(更现代的定时方案)。