22
第一个 Bash 脚本
基础脚本是按顺序执行的文本命令。shebang 指定解释器;也可明确用 bash 文件名执行。
cd "$HOME/shell-lab"
printf '#!/usr/bin/env bash\nprintf "hello shell\\n"\n' > hello.sh
bash hello.sh
bash -n hello.sh
- 观察结果
- 运行时输出 hello shell;bash -n 只检查语法,不执行脚本。
- 动手练习
- 给 hello.sh 加执行权限,再运行 `./hello.sh`。
- 注意
- 脚本没有执行权限时,可用 `bash hello.sh`;`bash -n` 不检查运行时逻辑。
官方参考 23
变量与引号
基础赋值号两侧不能加空格。双引号保留空格并允许变量展开;单引号把变量写成字面文本。
name='Linux Shell'
printf 'hello %s\n' "$name"
printf '%s\n' '$name'
printf '%s\n' "$name
- 观察结果
- 第二条打印字面文本 $name,第三条打印 Linux Shell。
- 动手练习
- 设 `file='my notes.txt'`,比较 `printf '%s\n' "$file"` 与未加引号的写法。
- 注意
- 处理路径和普通字符串时优先引用变量:`"$file"`;不要依赖词拆分。
官方参考 24
位置参数与参数检查
基础`$1` 是第一个参数,`$#` 是参数个数;`"$@"` 可保留各参数边界。
printf '#!/usr/bin/env bash\nprintf "count=%s\\n" "$#"\nprintf "arg=%s\\n" "$1"\n' > args.sh
bash args.sh hello
- 观察结果
- 输出 count=1 和 arg=hello。
- 动手练习
- 修改脚本,在没有参数时打印用法并以非零状态退出。
- 注意
- 启用 `set -u` 后,直接读取未传入的 `$1` 会报错;可用 `${1:-}` 或先检查 `$#`。
官方参考 25
条件判断与文件测试
基础`[[ ... ]]` 是 Bash 条件语法;`-f` 检查普通文件,`-d` 检查目录。if 根据命令退出状态选择分支。
file="$HOME/shell-lab/notes.txt"
if [[ -f "$file" ]]; then
printf 'found: %s\n' "$file"
else
printf 'missing\n'
fi
- 观察结果
- 若前面创建了 notes.txt,应输出 found。
- 动手练习
- 改成检查目录 `"$HOME/shell-lab"`,再试 `-d`。
- 注意
- `[[ ... ]]` 是 Bash 特性;如果脚本用 `/bin/sh` 执行,不能假定它可用。
官方参考 26
for 循环与数组
进阶for 可遍历列表;Bash 数组用括号创建,`"${items[@]}"` 保留每个元素。
items=('alpha one' 'beta two')
for item in "${items[@]}"; do
printf 'item: %s\n' "$item"
done
- 观察结果
- 输出两行,每个含空格的元素仍保持为一个整体。
- 动手练习
- 写一个循环,逐个打印练习目录下的 `*.log` 文件名。
- 注意
- 不要用 `for file in $(ls)` 处理文件名;空格、换行和通配符会使结果出错。
官方参考 27
while read 逐行读取
进阶`IFS= read -r` 保留行首尾空白和反斜杠;从文件重定向输入,避免把管道子 Shell 的变量变化当作主 Shell 状态。
cd "$HOME/shell-lab"
while IFS= read -r line; do
printf 'line: %s\n' "$line"
done < notes.txt
- 观察结果
- notes.txt 的每一行都加上 line: 前缀。
- 动手练习
- 计算文件行数,并比较 wc -l 的结果。
- 注意
- read 对没有结尾换行的最后一行会返回非零;生产脚本需考虑该边界。
官方参考 28
函数与局部变量
进阶函数封装可复用步骤;`local` 把变量作用域限制在 Bash 函数内。
greet() {
local name=${1:-friend}
printf 'hello %s\n' "$name"
}
greet shell
greet
- 观察结果
- 先输出 hello shell,再输出 hello friend。
- 动手练习
- 写一个函数接收文件名,检查文件是否存在并打印结果。
- 注意
- 函数的 return 表示退出状态,不适合返回任意字符串;字符串可用 stdout 输出。
官方参考 29
错误处理与 pipefail
进阶`set -e`、`-u`、`pipefail` 能帮助发现部分错误,但 `set -e` 在 if、&&、管道等上下文有例外,不能代替显式检查。
set -o pipefail
if grep -F 'missing-text' "$HOME/shell-lab/notes.txt" | wc -l; then
printf 'pipeline finished\n'
fi
set +o pipefail
- 观察结果
- grep 未匹配时退出 1;启用 pipefail 后,管道状态可反映该失败,但 wc 仍会输出 0。
- 动手练习
- 把 `grep -q` 用在 if 条件中,分别处理找到、没找到和读取出错。
- 注意
- 未匹配对 grep 来说不一定是异常;不要对所有非零状态机械报错。
官方参考 30
trap 清理临时资源
进阶trap 可在脚本退出时执行清理。临时文件要用 mktemp 创建,路径必须引用,避免误删其他文件。
tmp=$(mktemp)
trap 'rm -f -- "$tmp"' EXIT
printf 'temporary\n' > "$tmp"
cat -- "$tmp
- 观察结果
- 退出当前 Shell 或脚本时会清理临时文件;在交互终端试验前先理解 trap 的作用域。
- 动手练习
- 把这些命令写进单独脚本运行,检查临时文件是否消失。
- 注意
- 建议在独立脚本里演练 trap,避免覆盖交互式 Shell 已有的 EXIT 处理。
官方参考