教程

初学者的 Bash 脚本:第一个脚本、变量、条件、循环和 cron

更新于: · Skillaria.Top

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: с нуля до уверенного администрирования”结合实践详细讲解了这一主题。

前往课程