适用:bash(所有 Linux 默认) 耗时:约 35 分钟 前置:第一季 D6(10 条基本命令)
写脚本的门槛比你想的低得多。
你每天敲的那几条命令,原样粘到一个文件里,加上两行开头,它就是一个脚本。这一篇教你从"零"到"能干活"。
一、脚本是什么:把命令存起来
最小可用的脚本
#!/bin/bash
echo "你好"
就这么两行。
第一行 #!/bin/bash 叫 shebang,意思是"用 bash 来执行我"。必须有,且必须在第一行第一列。
跑起来
chmod +x hello.sh # 加执行权限(只需一次)
./hello.sh # 运行
为什么必须
./? 因为出于安全,系统不会从"当前目录"找可执行文件(否则你cd到一个有恶意ls的目录,敲ls就中招了)。
二、五个基础构件
1. 变量
#!/bin/bash
NAME="世界"
echo "你好,$NAME"
规则:
=两边不能有空格(NAME = "x"是错的)- 用的时候加
$,赋值时不加 - 建议全部加双引号:
"$NAME"(防止值里有空格时被拆开)
命令的结果存进变量:
NOW=$(date +%Y-%m-%d)
COUNT=$(ls | wc -l)
echo "今天是 $NOW,当前目录有 $COUNT 个文件"
2. 位置参数:让脚本能接收输入
#!/bin/bash
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "一共 $# 个参数"
echo "全部参数: $@"
echo "脚本名: $0"
./test.sh 苹果 香蕉
# 第一个参数: 苹果
# 第二个参数: 香蕉
# 一共 2 个参数
3. 判断(if)
#!/bin/bash
if [ -f "/etc/passwd" ]; then
echo "文件存在"
else
echo "文件不存在"
fi
注意方括号两边必须有空格:[ -f ... ] 对,[-f ...] 错。
常用判断:
| 写法 | 意思 |
|---|---|
[ -f 文件 ] | 是普通文件 |
[ -d 目录 ] | 是目录 |
[ -e 路径 ] | 存在(不管类型) |
[ -r 文件 ] | 可读 |
[ -w 文件 ] | 可写 |
[ -x 文件 ] | 可执行 |
[ -z "$VAR" ] | 字符串为空 |
[ -n "$VAR" ] | 字符串非空 |
[ "$a" = "$b" ] | 字符串相等 |
[ "$a" != "$b" ] | 字符串不等 |
[ "$a" -eq "$b" ] | 数字相等 |
[ "$a" -gt "$b" ] | 数字大于 |
[ "$a" -lt "$b" ] | 数字小于 |
⚠️ 字符串比较用
=,数字比较用-eq。用错会得到莫名其妙的结果([ "10" > "9" ]字符串比较是 false,因为 “1” < “9”)。
4. 循环(for / while)
for —— 遍历一组东西:
#!/bin/bash
for f in *.txt; do
echo "处理: $f"
done
遍历数字:
for i in {1..5}; do
echo "第 $i 次"
done
while —— 条件为真就一直循环:
#!/bin/bash
COUNT=1
while [ $COUNT -le 5 ]; do
echo "计数: $COUNT"
COUNT=$((COUNT + 1))
done
读文件逐行处理(最常用的模式):
#!/bin/bash
while IFS= read -r line; do
echo "行: $line"
done < 文件.txt
IFS= read -r line这个写法看起来怪,但它是正确的读行方式。IFS=防止行首尾空格被吃掉,-r防止反斜杠被解释。
5. 函数
#!/bin/bash
greet() {
echo "你好,$1"
}
greet "张三"
greet "李四"
函数就是"给一段命令起个名字"。 复杂脚本里用它,能少写很多重复。
三、让脚本"像个正经程序"
1. 出错就停(强烈建议)
#!/bin/bash
set -euo pipefail
这一行是三个开关:
| 开关 | 作用 |
|---|---|
-e | 任何命令失败就立刻退出(不然错了还继续跑,后果可能很糟) |
-u | 用未定义的变量就报错(防手滑打错变量名) |
-o pipefail | 管道里任何一环失败都算失败 |
除了最简单的脚本,都建议加这一行。
2. 有输出、有提示
#!/bin/bash
set -euo pipefail
echo "▶ 开始备份..."
# ...干活...
echo "✅ 备份完成"
打日志很重要——脚本是"黑盒",出问题时只有 echo 能告诉你它跑到哪了。
3. 检查参数
#!/bin/bash
set -euo pipefail
if [ $# -lt 1 ]; then
echo "用法: $0 <目录>"
exit 1
fi
TARGET="$1"
if [ ! -d "$TARGET" ]; then
echo "错误: $TARGET 不是目录"
exit 1
fi
这是"专业"和"业余"的分界线:业余脚本假设输入永远对,专业脚本先检查。
4. 用 set -x 调试
脚本不按预期跑?在第二行加:
set -x
它会把每条实际执行的命令打出来(前面带 +)。调试完记得删掉。
四、实战:三个能直接用的脚本
脚本 1:备份目录
#!/bin/bash
set -euo pipefail
# 用法: ./backup.sh <源目录> <目标目录>
if [ $# -lt 2 ]; then
echo "用法: $0 <源目录> <目标目录>"
exit 1
fi
SRC="$1"
DEST="$2/backup-$(date +%Y%m%d-%H%M%S)"
if [ ! -d "$SRC" ]; then
echo "错误: 源目录不存在: $SRC"
exit 1
fi
mkdir -p "$DEST"
echo "▶ 开始备份 $SRC → $DEST"
rsync -av --progress "$SRC/" "$DEST/" | tail -5
echo "✅ 完成,占用空间:"
du -sh "$DEST"
用了什么:参数检查、日期命令替换、rsync(增量复制,比 cp 快)。
脚本 2:磁盘监控(超阈值告警)
#!/bin/bash
set -euo pipefail
THRESHOLD=85
# 取根分区的使用率(去掉 % 号)
USAGE=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
echo "当前根分区使用率: ${USAGE}%"
if [ "$USAGE" -ge "$THRESHOLD" ]; then
echo "⚠️ 警告: 磁盘使用率超过 ${THRESHOLD}%"
echo "占用最大的 5 个目录:"
du -sh /* 2>/dev/null | sort -h | tail -5
exit 1
fi
echo "✅ 磁盘正常"
用了什么:awk 取字段、tr 去字符、数字比较、条件退出。
脚本 3:批量重命名(给文件加日期前缀)
#!/bin/bash
set -euo pipefail
# 用法: ./prefix.sh <目录>
DIR="${1:-.}"
PREFIX=$(date +%Y%m%d)
cd "$DIR"
count=0
for f in *.jpg; do
[ -e "$f" ] || continue # 没有 jpg 时 *.jpg 不会展开,要跳过
mv -n "$f" "${PREFIX}_${f}"
count=$((count + 1))
done
echo "✅ 重命名了 $count 个文件"
用了什么:默认值 ${1:-.}、[ -e "$f" ] || continue(空目录保护)、mv -n(不覆盖已有文件)。
[ -e "$f" ] || continue这一行很关键:目录里没有.jpg时,*.jpg不会被 shell 展开,f的值就是字面量*.jpg,导致后面mv报错。这是 shell 脚本的经典坑。
五、定时执行:cron
脚本写好了,让它自动跑。
crontab -e # 第一次会让你选编辑器,选 nano
格式(五个星号 + 命令):
分 时 日 月 周 命令
* * * * * command
| 字段 | 范围 |
|---|---|
| 分 | 0-59 |
| 时 | 0-23 |
| 日 | 1-31 |
| 月 | 1-12 |
| 周 | 0-7(0 和 7 都是周日) |
常用例子:
# 每天 2:00 备份
0 2 * * * /home/用户名/backup.sh /home/用户名/文档 /mnt/backup >> /home/用户名/backup.log 2>&1
# 每 5 分钟检查磁盘
*/5 * * * * /home/用户名/diskcheck.sh
# 每周一 9:00
0 9 * * 1 /home/用户名/weekly.sh
# 每小时的第 0 分钟
0 * * * * /home/用户名/hourly.sh
三个必须注意的:
用绝对路径(cron 的 PATH 很短,写
python3可能找不到)# 保险写法 0 2 * * * export PATH=/usr/local/bin:/usr/bin:/bin; /home/用户名/backup.sh>> 日志 2>&1一定要加,否则出错了你完全不知道%在 crontab 里要转义成\%(这是 crontab 的坑,很少人知道)
查看当前任务:
crontab -l
cron 的日志(排查"为什么没跑"):
journalctl -u cron -n 50 # Debian 系
journalctl -u crond -n 50 # RPM 系
grep CRON /var/log/syslog | tail -20
六、动手:三个练习
练习 1:写并运行第一个脚本
cat > ~/hello.sh << 'EOF'
#!/bin/bash
set -euo pipefail
echo "当前时间: $(date)"
echo "当前目录: $(pwd)"
echo "登录用户: $(whoami)"
echo "磁盘使用: $(df -h / | awk 'NR==2 {print $5}')"
EOF
chmod +x ~/hello.sh
~/hello.sh
练习 2:加参数和判断
cat > ~/check.sh << 'EOF'
#!/bin/bash
set -euo pipefail
if [ $# -lt 1 ]; then
echo "用法: $0 <文件路径>"
exit 1
fi
if [ -f "$1" ]; then
echo "✅ $1 是文件,大小 $(du -h "$1" | cut -f1)"
elif [ -d "$1" ]; then
echo "✅ $1 是目录,里面 $(ls -1 "$1" | wc -l) 个东西"
else
echo "❌ $1 不存在"
exit 1
fi
EOF
chmod +x ~/check.sh
~/check.sh /etc/passwd
~/check.sh /etc
~/check.sh /不存在
练习 3:跑一个循环并统计
cat > ~/count.sh << 'EOF'
#!/bin/bash
set -euo pipefail
total=0
for f in ~/*; do
if [ -f "$f" ]; then
total=$((total + 1))
fi
done
echo "家目录下有 $total 个文件"
EOF
chmod +x ~/count.sh
~/count.sh
七、验证:你过关了吗
- 会写
#!/bin/bash并有chmod +x - 会用变量、
$(命令)、位置参数 - 会写
if [ -f ... ]判断 - 会写
for循环 - 知道
set -euo pipefail是干什么的 - 会用
set -x调试 - 会写一条 cron 任务,知道要加
>> log 2>&1
八、不行怎么办
Q1:./script.sh: Permission denied
chmod +x script.sh
Q2:bad interpreter: No such file or directory
shebang 写错了,或者文件是 Windows 换行:
head -1 script.sh # 看 shebang
file script.sh # 如果显示 CRLF,就是 Windows 换行
# 修:
sed -i 's/\r$//' script.sh
这个坑在从 Windows 复制脚本过来时必踩。
Q3:[ : command not found 或 [xxx: not found
方括号两边没空格。必须是 [ -f x ],不能是 [-f x]。
Q4:脚本手动跑正常,cron 里跑失败 两个原因:
- PATH 不一样 → 用绝对路径
- 工作目录不一样 → 脚本开头
cd /正确的/目录
Q5:变量是空的
- 定义时不能有空格:
A = 1错,A=1对 - 用的时候要加
$和引号:"$A"
Q6:integer expression expected
用 -eq 比较了非数字。检查变量里是不是空的或含非数字字符:
echo "[$VAR]" # 用方括号包着看,能看出有没有隐藏空格
Q7:循环里 *.txt 没匹配到文件
目录里没 txt 时,*.txt 不展开,变成字面量。加保护:
for f in *.txt; do
[ -e "$f" ] || continue
...
done
九、一句话总结
脚本 = 你敲过的命令 +
#!/bin/bash+chmod +x。进阶只有三步:加
set -euo pipefail、检查参数、用 cron 定时跑。别追求"高级"。能省你十分钟的脚本,就是好脚本。
← 上一篇 E5|磁盘与挂载 下一篇 → E7|用户与权限管理:一台机器多人用