适用: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

三个必须注意的:

  1. 用绝对路径(cron 的 PATH 很短,写 python3 可能找不到)

    # 保险写法
    0 2 * * * export PATH=/usr/local/bin:/usr/bin:/bin; /home/用户名/backup.sh
    
  2. >> 日志 2>&1 一定要加,否则出错了你完全不知道

  3. % 在 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 里跑失败 两个原因:

  1. PATH 不一样 → 用绝对路径
  2. 工作目录不一样 → 脚本开头 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|用户与权限管理:一台机器多人用