初学者的 Bash 脚本:第一个脚本、变量、条件、循环和 cron
Bash 脚本是包含 shell 命令的文本文件,第一行为 #!/bin/bash,通过 chmod +x 获得执行权限后以 ./script.sh 运行。脚本中常用变量、参数 $1 和 $2、if 条件、for 与 while 循环;定时运行则通过 crontab -e 配置。
Bash 脚本是普通的文本文件,里面的命令原本你要逐条在终端中输入。脚本以 shebang 行 #!/bin/bash 开头,通过 chmod +x 获得执行权限,然后以 ./script.sh 的方式运行。本文是一份面向初学者的 bash 脚本教程,从第一行代码讲到定时运行:变量、参数、条件、循环、错误处理和 cron。
一分钟写出第一个 bash 脚本
用任意编辑器创建文件,例如 nano:
nano hello.sh
#!/bin/bash
# 第一个脚本:问候并显示一些系统信息
echo "你好,$USER!"
echo "今天是:$(date +%Y-%m-%d)"
echo "你现在位于:$(pwd)"
保存(Ctrl+O、回车、Ctrl+X),赋予执行权限,然后运行:
$ chmod +x hello.sh
$ ./hello.sh
你好,alice!
今天是:2026-10-12
你现在位于:/home/alice
以 # 开头的行是注释。$(命令) 会把命令的输出直接插入到字符串中。
Shebang:第一行的作用
#!/bin/bash 告诉系统使用哪个解释器来执行文件。如果没有这一行,脚本会由当前可用的任意 shell 运行;而在某些系统中,/bin/sh 并不是 bash,而是更简单的 dash,其中一部分语法无法工作。
也经常会见到 #!/usr/bin/env bash:它在 PATH 中查找 bash,当 bash 没有安装在 /bin 下时比较方便。
如何运行脚本,以及为什么要加 ./
有两种方式:
./hello.sh:需要 shebang 行和执行权限x。前缀./是必需的,因为当前目录不在PATH中。bash hello.sh:不需要执行权限,因为解释器是明确指定的。
如果不加 ./ 时看到 command not found,没有 chmod +x 时看到 Permission denied,原因正是这两点。想在任何位置都能按名字调用自己的脚本,可以把它放到 ~/bin 或 /usr/local/bin 中。
变量
给变量赋值时,等号两边不能有空格;读取时使用 $:
#!/bin/bash
name="backup"
dir="/var/backups"
today=$(date +%F)
echo "文件:${dir}/${name}-${today}.tar.gz"
花括号把变量名与紧随其后的文本分开。写成 name = "backup"(带空格)是错误的:bash 会尝试运行名为 name 的命令。
引号很重要
在双引号中,变量会被替换;在单引号中,则不会。变量几乎总是应该放在双引号中,否则包含空格的文件名会被拆分成两个参数。
file="我的 报告.txt"
rm $file # 错误:会尝试删除“我的”和“报告.txt”
rm "$file" # 正确
echo '$HOME' # 原样输出 $HOME
echo "$HOME" # 输出 /home/alice
环境变量
$HOME、$USER、$PATH 等变量由系统设置。自定义变量可以用 export 传递给子进程。永久性的环境设置写在 ~/.bashrc 或 ~/.profile 中。
脚本参数
运行时传入的值可以通过 $1、$2 等访问。
| 变量 | 内容 |
|---|---|
$0 | 脚本名称 |
$1、$2…… | 第一个、第二个参数 |
$# | 参数个数 |
"$@" | 逐个列出的所有参数 |
$? | 上一条命令的退出码 |
$$ | 当前脚本的 PID |
#!/bin/bash
echo "脚本 $0 收到 $# 个参数"
echo "第一个:$1"
$ ./args.sh nginx 80
脚本 ./args.sh 收到 2 个参数
第一个:nginx
用户输入通过 read 命令读取:read -p "请输入姓名:" name。
条件 if
每条命令都会返回退出码:0 表示成功,其他值表示错误。if 正是检查这个退出码。比较通常使用 [[ ... ]],方括号内部的空格不能省略。
#!/bin/bash
if [[ $# -eq 0 ]]; then
echo "用法:$0 <目录>"
exit 1
fi
if [[ -d "$1" ]]; then
echo "目录 $1 存在"
elif [[ -f "$1" ]]; then
echo "$1 是文件,而不是目录"
else
echo "$1 未找到"
fi
| 检查 | 含义 |
|---|---|
-f file | 普通文件存在 |
-d dir | 目录存在 |
-e path | 路径存在 |
-x file | 文件可执行 |
-z "$var" / -n "$var" | 字符串为空 / 非空 |
"$a" == "$b"、!= | 字符串比较 |
-eq -ne -lt -le -gt -ge | 数字比较 |
也可以直接检查任意命令的结果:
if systemctl is-active --quiet nginx; then
echo "nginx 正在运行"
else
echo "nginx 已停止"
fi
选项较多时,case 更方便:
case "$1" in
start) echo "启动" ;;
stop) echo "停止" ;;
*) echo "未知命令:$1"; exit 1 ;;
esac
循环 for 和 while
for:遍历列表
for host in web1 web2 db1; do
echo "正在检查 $host"
ping -c 1 -W 1 "$host" > /dev/null && echo " 可访问" || echo " 不可访问"
done
for file in /var/log/*.log; do
echo "$file:$(wc -l < "$file") 行"
done
for i in {1..5}; do
echo "第 $i 次尝试"
done
while:只要条件成立
经典例子是逐行读取文件:
while read -r user; do
echo "用户:$user"
done < users.txt
等待服务启动:
count=0
until curl -sf http://localhost:8080/health > /dev/null; do
count=$((count + 1))
if [[ $count -ge 10 ]]; then
echo "服务未能启动"
exit 1
fi
sleep 3
done
echo "服务已就绪"
bash 的算术运算使用双括号:$((a + b))。它只支持整数。
函数
重复的代码可以放进函数中。函数内部同样使用 $1、$2 等参数,而 local 可以防止变量泄露到外部。
log() {
local level="$1"; shift
echo "$(date '+%F %T') [$level] $*"
}
log INFO "开始备份"
log ERROR "磁盘空间不足"
错误处理:set -euo pipefail
默认情况下,bash 在命令失败后仍会继续执行。这样,备份脚本可能会“成功”结束,但实际上什么都没有复制。因此,通常在脚本开头加入严格模式:
#!/bin/bash
set -euo pipefail
-e:遇到第一个错误就停止。-u:引用未声明的变量视为错误(可以防止变量为空时执行rm -rf "$DIR/"这类命令)。-o pipefail:只要管道中的任意一部分失败,整个管道就视为失败。
调试时,可以使用 bash -x script.sh 运行脚本,bash 会在执行每条命令前把它打印出来。在运行之前,shellcheck 工具可以帮助发现常见错误。
实例:备份脚本
#!/bin/bash
set -euo pipefail
SRC="${1:-/var/www}"
DST="/var/backups/site"
KEEP_DAYS=7
STAMP=$(date +%F_%H-%M)
mkdir -p "$DST"
tar -czf "$DST/site-$STAMP.tar.gz" -C "$(dirname "$SRC")" "$(basename "$SRC")"
echo "已创建归档 $DST/site-$STAMP.tar.gz"
# 删除超过 KEEP_DAYS 天的归档
find "$DST" -name "site-*.tar.gz" -mtime +"$KEEP_DAYS" -delete
写法 ${1:-/var/www} 的意思是“使用第一个参数;如果没有提供,则使用 /var/www”。把脚本保存为 /usr/local/bin/backup-site.sh,并使其可执行:sudo chmod +x /usr/local/bin/backup-site.sh。
定时运行:cron
cron 是按计划运行命令的服务。用户的任务通过 crontab -e 编辑,用 crontab -l 查看;root 的任务通过 sudo crontab -e 编辑。
任务的每一行由五个时间字段和命令组成:
# 分 时 日 月 周 命令
30 2 * * * /usr/local/bin/backup-site.sh >> /var/log/backup.log 2>&1
| 时间表达式 | 运行时间 |
|---|---|
*/5 * * * * | 每 5 分钟 |
0 * * * * | 每个整点 |
30 2 * * * | 每天 02:30 |
0 9 * * 1-5 | 工作日 09:00 |
0 3 1 * * | 每月 1 日 03:00 |
@reboot | 启动后运行一次 |
为什么脚本手动运行正常,在 cron 中却不行
- 环境不同。cron 使用极简的
PATH,并且不读取你的~/.bashrc。请使用命令和文件的完整路径,或在 crontab 开头设置PATH。 - 当前目录不同。任务启动时位于主目录,而不是脚本所在的目录。请使用绝对路径。
- 百分号 %。在 crontab 中,
%表示换行,因此直接写在任务行中的date +%F需要转义为\%。更简单的办法是把命令放进脚本里。 - 输出丢失。把输出重定向到日志(
>> file 2>&1),否则看不到错误。cron 的运行记录可以通过journalctl -u cron查看(在 RHEL 系列系统中,服务名为crond)。
在使用 systemd 的系统中,还可以用 systemd 定时器代替 cron:定时器的输出会写入日志,并且能补齐错过的运行。
总结
Bash 脚本入门其实就是:一个带有 #!/bin/bash 的文件,赋予执行权限,再加上一连串命令。给变量加上引号,用 if 检查参数,使用 for 和 while 循环,并开启严格模式 set -euo pipefail,你就得到了一个可靠的自动化工具,cron 可以按计划运行它。bash 脚本、环境和 cron 的内容都会在 Linux 课程中结合实践讲解。
常见问题
bash 和 sh 有什么区别?
sh 是标准的 POSIX shell,bash 则是它的扩展版本,提供数组、双括号和其他便利功能。使用了 bash 语法的脚本必须用 bash 运行,而不能用 sh。
bash 脚本应该使用什么扩展名?
Linux 并不看扩展名,解释器是由 shebang 决定的。.sh 扩展名只是一种约定,方便人们识别这是脚本。
怎样安全地把密码传给脚本?
不要用参数传递,因为参数会在 ps 中被其他用户看到。可以使用权限为 600 的文件中保存的环境变量,或者用 read -s 无回显地输入。
怎样检查脚本是否以 root 身份运行?
比较用户 ID 与 0:if [[ $EUID -ne 0 ]]; then echo "需要 root 权限"; exit 1; fi。
怎样让脚本在退出 SSH 后继续在后台运行?
使用 nohup ./script.sh &,或者使用 tmux、screen 会话;如果需要长期运行,可以把脚本做成 systemd 服务。
课程“Linux: с нуля до уверенного администрирования”结合实践详细讲解了这一主题。
- Bash-скрипты: от команды в терминале до рабочего скрипта автоматизации
- Пакеты, окружение и первая автоматизация: apt, профиль и cron