Linux: Bash 脚本基础——变量 / 条件 / 循环

将一系列 Linux 命令保存到文件,加上一些逻辑控制(条件、循环),就变成了一个 Shell 脚本。它是自动化一切重复工作的起点。

📋 前置知识:需要先掌握以下内容

1. 你将学到


2. 一个从手动部署到一键部署的故事

(1) 痛点:每天都要敲同样的 5 条命令

小明每天部署项目都要依次执行:git pullnpm installnpm run buildpm2 restart appecho "done"。5 条命令,3 分钟,每天都在重复。

(2) 一个 8 行的脚本

他写了一个 deploy.sh

BASH
#!/bin/bash
set -e
echo "开始部署..."

cd /var/www/myapp
git pull origin main
npm install
npm run build
pm2 restart app
echo "✅ 部署完成!"
💡 提示set -e 是脚本安全的第一道防线——任何命令返回非零退出码时脚本立即停止。没有 set -e,脚本会继续执行后续命令,即使前面的命令已经失败,可能导致严重的连锁错误(如用破损的代码重启服务)。

(3) 收益:一键部署

从此小明只需执行 ./deploy.sh,所有步骤自动完成,节省时间、避免遗漏。


3. 知识点讲解

(1) Shebang

脚本的第一行必须是 Shebang——告诉系统用哪个解释器执行:

TEXT 📖 仅展示
#!/bin/bash      # 用 bash 执行
#!/bin/sh        # 用 sh(兼容模式)
#!/usr/bin/env python3  # 用 Python 3
💡 提示#!/bin/bash 是 Shebang——它告诉操作系统用哪个解释器执行脚本。没有这一行,系统会用当前 Shell 执行,可能导致兼容性问题(如 sh 不支持 [[ ]] 语法)。所有 bash 脚本都必须以 #!/bin/bash 开头。

(2) 变量

TEXT 📖 仅展示
# 定义变量(等号两边不能有空格)
name="Alice"
age=25

# 使用变量(推荐用花括号包裹)
echo $name          # Alice
echo ${name}        # Alice(花括号更安全)
echo "你好, ${name}"

# 变量命名规则
# 1. 字母、数字、下划线组成
# 2. 不能以数字开头
# 3. 区分大小写(NAME != name)

三种引号的区别:

引号 示例 行为
双引号 "Hello $name" 解析变量和命令
单引号 'Hello $name' 字面字符串,不解析
反引号/$() `date`$(date) 执行命令,取输出

ℹ️ 说明:单引号和双引号的核心区别——双引号中的 $变量$(命令) 会被解析替换,单引号中的一切都是字面量。需要拼接变量时用双引号,需要保留 $ 等特殊字符时用单引号(如正则表达式、heredoc 中不想展开变量的场景)。

TEXT 📖 仅展示
name="Alice"
echo "Hello $name"     # Hello Alice
echo 'Hello $name'     # Hello $name
echo "今天 $(date)"    # 今天 Tue Jul  7 10:23:45 CST 2026

(3) 位置参数

TEXT 📖 仅展示
./script.sh arg1 arg2 arg3
#           $1    $2    $3

$0       # 脚本名本身
$1, $2   # 第 1、2 个参数
$#       # 参数个数
$@       # 所有参数(分别作为独立参数)
$*       # 所有参数(作为一个字符串)
$?       # 上一个命令的退出码(0=成功,非0=失败)
$$       # 当前脚本的 PID

(4) 条件判断(if / test / [[ ]])

TEXT 📖 仅展示
# test 命令 或 [ ]
if [ "$name" = "Alice" ]; then
    echo "Hello Alice"
fi

# 更强大的 [[ ]](bash 特有)
if [[ "$name" == "Alice" ]]; then
    echo "Hello Alice"
fi

# if-elif-else
if [ "$1" -gt 10 ]; then
    echo "大于 10"
elif [ "$1" -gt 5 ]; then
    echo "大于 5"
else
    echo "小于等于 5"
fi

文件判断:

条件 含义
-f file 是否为普通文件
-d dir 是否为目录
-e path 是否存在
-s file 文件大小 > 0
-r file 是否可读
-w file 是否可写
-x file 是否可执行

字符串比较:

条件 含义
$a = $b 相等
$a != $b 不等
-z $a 字符串为空(长度 = 0)
-n $a 字符串不为空

数字比较:

条件 含义
$a -eq $b 相等
$a -ne $b 不等
$a -gt $b 大于
$a -ge $b 大于等于
$a -lt $b 小于
$a -le $b 小于等于

(5) 循环

BASH
# for 循环
for i in 1 2 3 4 5; do
    echo "数字: $i"
done

for file in *.txt; do
    echo "找到文件: $file"
done

for i in {1..10}; do
    echo "$i"
done

# while 循环
count=1
while [ $count -le 5 ]; do
    echo "第 $count 次"
    count=$((count + 1))
done

# 读取文件逐行
while IFS= read -r line; do
    echo "$line"
done < /etc/hostname

▶ 示例:基础脚本

BASH
#!/bin/bash
# hello.sh - 第一个 bash 脚本
echo "Hello, World!"
echo "当前用户: $(whoami)"
echo "当前目录: $(pwd)"
echo "文件数: $(ls | wc -l)"

输出:

TEXT 📖 仅展示
Hello, World!
当前用户: alice
当前目录: /home/alice
文件数: 12

▶ 示例:使用位置参数

TEXT 📖 仅展示
#!/bin/bash
# greet.sh - 向指定用户打招呼
if [ $# -eq 0 ]; then
    echo "用法: $0 <用户名>"
    exit 1
fi

echo "你好, $1!"
echo "欢迎使用 Linux 脚本。"
echo "脚本名: $0"
echo "参数数量: $#"
TEXT 📖 仅展示
./greet.sh Alice
# 你好, Alice!

▶ 示例:if 条件判断

TEXT 📖 仅展示
#!/bin/bash
# check-file.sh - 检查文件状态

FILE="${1:-test.txt}"

if [ ! -e "$FILE" ]; then
    echo "文件 '$FILE' 不存在"
    exit 1
fi

if [ -f "$FILE" ]; then
    echo "类型: 普通文件"
elif [ -d "$FILE" ]; then
    echo "类型: 目录"
fi

[ -r "$FILE" ] && echo "可读: ✅" || echo "可读: ❌"
[ -w "$FILE" ] && echo "可写: ✅" || echo "可写: ❌"
[ -x "$FILE" ] && echo "可执行: ✅" || echo "可执行: ❌"

▶ 示例:for 循环批量操作

TEXT 📖 仅展示
#!/bin/bash
# rename.sh - 批量重命名 .txt 为 .md

count=0
for file in *.txt; do
    if [ -f "$file" ]; then
        mv "$file" "${file%.txt}.md"
        echo "重命名: $file → ${file%.txt}.md"
        count=$((count + 1))
    fi
done

echo "总共重命名了 $count 个文件"

▶ 示例:while 循环与读取输入

TEXT 📖 仅展示
#!/bin/bash
# countdown.sh - 倒计时

if [ $# -ne 1 ]; then
    echo "用法: $0 <秒数>"
    exit 1
fi

count=$1
while [ $count -gt 0 ]; do
    echo "倒计时: $count"
    count=$((count - 1))
    sleep 1
done

echo "时间到!"

▶ 综合示例:批量文件处理脚本

BASH
#!/bin/bash
# batch-process.sh - 批量处理目录中的文件

DIR="${1:-.}"
ACTION="${2:-info}"

if [ ! -d "$DIR" ]; then
    echo "错误: 目录 '$DIR' 不存在"
    exit 1
fi

echo "处理目录: $DIR"
echo "操作: $ACTION"
echo "---"

case "$ACTION" in
    info)
        echo "文件统计:"
        echo "  总文件数: $(find "$DIR" -type f | wc -l)"
        echo "  总目录数: $(find "$DIR" -type d | wc -l)"
        echo "  总大小: $(du -sh "$DIR" | cut -f1)"
        ;;
    backup)
        BACKUP="/tmp/backup-$(date +%Y%m%d)"
        mkdir -p "$BACKUP"
        cp -r "$DIR" "$BACKUP/"
        echo "备份完成: $BACKUP"
        ;;
    clean)
        echo "清理 .log 文件..."
        find "$DIR" -name "*.log" -delete
        echo "清理 .tmp 文件..."
        find "$DIR" -name "*.tmp" -delete
        echo "清理完成"
        ;;
    *)
        echo "操作: info / backup / clean"
        ;;
esac

❓ 常见问题

Q: 脚本第一行的 #!/bin/bash 必须写吗? A: 必须有。它告诉内核用哪个解释器来执行这个脚本。如果不写,默认用当前 Shell 执行(可能造成兼容性问题)。

Q: 为什么我写了脚本却提示 command not foundA: 两个原因:1)没有执行权限 chmod +x script.sh。2)当前目录不在 PATH 中,需要用 ./script.sh 而不是 script.sh

Q: $@$* 有什么区别? A: $@ 将每个参数作为独立的"单词",$* 将所有参数合并为一个字符串。大多数场景推荐用 $@。遍历参数时 for arg in "$@"; do 是标准做法。

Q: exit 0exit 1 有什么区别? A: exit 0 表示成功,exit 非0 表示失败(不同的数字表示不同的错误类型)。其他程序可以通过检查 $? 知道脚本是否成功执行。

Q: 脚本里怎么获取用户的输入? A:read 命令:read -p "请输入名字: " name,然后 $name 就是用户的输入。


📖 小节


📝 作业

  1. 基础题(难度⭐):写一个脚本 info.sh,输出当前用户名、日期、当前目录和文件数量
  2. 进阶题(难度⭐⭐):写一个脚本 check-file.sh,接受一个文件名参数,检查文件是否存在并输出文件类型,然后写一个脚本 backup-txt.sh,将当前目录所有 .txt 文件备份到 /tmp/txt-backup/
  3. 挑战题(难度⭐⭐⭐):写一个倒计时脚本,接受秒数参数并逐秒倒计时,加上 set -e 错误处理;然后用 for 循环创建 10 个以 test- 开头、编号结尾的目录,最后写一个脚本统计这 10 个目录的大小并排序输出
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏