Bash 命令执行:分号、条件运算符、别名与环境变量
Bash 读取命令,解释名称和参数,完成必要的展开与重定向后执行。分号 ; 不论前一步结果如何都执行下一条命令,&& 只在成功后继续,|| 只在退出状态非零时继续。别名在 Shell 读取命令时替换文本,环境变量则把设置传给子进程,二者的作用不同。
初学时先掌握变量、环境变量、别名、退出状态和基本条件执行。命令组、管道、后台任务与完整脚本可按需要深入阅读。把规则写入文件的方法见第一个 Bash 脚本(英语);失败处理、路径和并发的限制见Bash 错误处理(英语)。
命令名称如何解析?
以下例子以 Bash 为准,不能把所有 Shell 的扩展语法混用。命令名称可能对应别名、函数、Shell 内建命令或通过 PATH 找到的外部程序,同名并不代表执行的是同一种实现。先检查当前 Shell:
type -a printf
type -a ls
command -v ls
type -a 可以列出别名、函数、内建命令和可执行文件候选;command -v 用于确认名称能否解析。具体输出取决于本机配置。PATH 是搜索外部程序的目录列表,不能用一个文件路径概括所有别名或函数。不要为练习覆盖系统 PATH。
退出状态与执行符号
退出状态(exit status)是命令结束后留给 Shell 的状态值。0 通常表示成功,非零表示失败或该命令定义的其他结果,例如 grep 无匹配时可返回 1。$? 表示最近一条命令的状态,应立即读取或保存;再执行其他命令就会改变它。
true
printf 'status=%s\n' "$?"
false
printf 'status=%s\n' "$?"
依次输出 status=0 和 status=1。条件执行依据状态值,而不是屏幕上的文字是否像成功提示。Bash 找不到命令时返回 127,找到但不能执行时返回 126;被信号终止也有相应约定,不能把所有失败都当成 1。
| 符号 | 作用 |
|---|---|
; |
前一命令结束后,无论成功或失败都执行下一条 |
&& |
仅在前一步成功后执行下一条 |
|| |
仅在前一步状态非零时执行下一条 |
& |
异步执行,不等待任务结束 |
| |
把标准输出接到下一条命令的标准输入 |
|& |
把标准输出与标准错误一起传入管道 |
! |
对命令或管道的退出状态作逻辑取反 |
在 Bash 语法中,;、&、&&、|| 是命令列表运算符;| 与 |& 构成管道,! 是对管道状态取反的保留字。练习使用 Bash 及 true、false、printf、grep、cat、sleep、ls、printenv,Ubuntu 和 Rocky Linux 的常见环境通常提供这些命令,无需管理员权限。
按成功或失败连接命令
分号与条件执行的区别
; 等待前一条命令结束后继续,不检查它是否成功。在普通命令列表中,换行也能分隔命令;但未闭合的引号、反斜杠、管道或未完成的复合命令可能使 Bash 继续读取,换行不一定结束命令。
false; printf 'always\n'
false && printf 'not run\n'
false || printf 'fallback\n'
true && printf 'next step\n'
依次输出 always、fallback、next step,不会输出 not run。&& 与 || 使用短路求值:不满足条件就跳过右侧命令。例如 cd /actual/work/path && work_command 可避免切换目录失败后在错误位置执行工作;这只是模式,必须把路径和命令换成已准备好的实际值。
求值顺序不等于 if 分支
&& 与 || 优先级相同,从左向右结合。因此 A && B || C 中,即使 A 成功,只要 B 失败,C 也会执行。
true && false || printf 'fallback runs\n'
结果是 fallback runs。若只想由最初条件决定选哪一个分支,应使用 if:
if true; then
false
else
printf 'condition failed\n'
fi
这里没有输出。选中 then 后,内部的 false 失败也不会转入 else,但该失败并未自动恢复。实际脚本仍须检查步骤结果并返回适当状态。
输出错误提示时保留失败状态
false || printf 'failure reported\n'
printf 'list status=%s\n' "$?"
先输出 failure reported,再输出 list status=0。|| 本身不意味着“忽略错误”,这里是最后实际执行的 printf 成功,使列表的最终状态变为 0。若脚本必须在报告后仍以失败结束,应先保存原始状态,再通过 exit 返回。
把多条命令组成一个任务
花括号在当前 Shell 中运行
{ ...; } 在当前 Shell 环境中执行命令组。花括号与命令之间要留空白,右花括号前需要分号或换行。下面会暂时改变练习变量;若同名变量已有用途,请先换用自己的名称。
LESSON_SCOPE=outer
{ LESSON_SCOPE=inner; printf '%s\n' "$LESSON_SCOPE"; }
printf '%s\n' "$LESSON_SCOPE"
unset LESSON_SCOPE
inner 输出两次,因为组内赋值保留在当前 Shell。末尾的 unset 删除练习变量。
圆括号使用子 Shell
LESSON_SCOPE=outer
( LESSON_SCOPE=inner; printf '%s\n' "$LESSON_SCOPE" )
printf '%s\n' "$LESSON_SCOPE"
unset LESSON_SCOPE
依次输出 inner 和 outer。圆括号建立子 Shell(subshell)执行环境,普通变量或当前目录的变化不留在外层 Shell。但它不会撤销创建、删除文件或请求外部服务等外部效果,不能把子 Shell 当作事务回滚。
管道、标准错误与状态取反
管道不是条件执行
printf 'alpha\nbeta\n' | grep 'alpha'
只输出 alpha。| 把左侧标准输出(stdout)接到右侧标准输入(stdin);|| 则根据失败状态选择执行命令。管道内的命令处理相连的数据流,不是等前一步成功后才依次放行。文件重定向的详细练习见Bash 标准输入输出与重定向。
用 |& 一并传递标准错误
bash -c 'printf "normal\n"; printf "error\n" >&2' |& cat
normal 和 error 都经由 cat 输出。本例中,Bash 的 |& 类似 2>&1 |,把标准错误(stderr)并入标准输出再送入管道。若已有其他重定向,还须检查处理顺序,不能机械替换符号。下游使用 tee 可同时显示和记录数据,但此时正常输出与错误已合并。
! 反转状态,不改变输出
! false
printf 'inverted status=%s\n' "$?"
输出 inverted status=0:原本失败的 false 经逻辑取反后变为成功。原状态为 0 时,! 将其变为非零。它不反转错误文字或文件内容。
用 pipefail 检查管道失败
( set +o pipefail; false | cat; printf 'default=%s\n' "$?" )
( set -o pipefail; false | cat; printf 'pipefail=%s\n' "$?" )
依次输出 default=0、pipefail=1。默认情况下管道返回最后一条命令的状态,因此 cat 成功会掩盖前面的 false。启用 pipefail 后,全部成功才返回 0,否则取最右侧非零退出状态。两次比较都在圆括号内运行,不会改变外层 Shell 选项。非零状态也可能是 grep 无匹配等预期结果,应结合业务解释。
后台执行后如何确认完成?
& 启动异步任务,使 Shell 不必等待它完成;不要与成功后才继续的 && 混淆。下面先启动短暂等待的任务,再显式等待它结束:
sleep 2 &
lesson_job=$!
printf 'foreground continues\n'
wait "$lesson_job"
printf 'job status=%s\n' "$?"
unset lesson_job
先看到 foreground continues,任务结束后看到 job status=0。$! 是最近后台任务的进程 ID,应及时保存;启动后立即读取的 $? 不能证明后台任务最终成功。wait 返回被等待任务的状态,这里紧接着读取。
交互式 Bash 的 jobs、fg、bg 用于查看任务以及前后台切换。只加 & 不保证退出登录后继续运行;长期任务应选择合适的会话管理或服务管理方式。
用完整脚本练习成功与失败流程
下例仅用消息模拟构建、测试和部署顺序,不会真正构建、部署或删除文件。在一个空练习目录中新建 course15-flow.sh 并保存以下全部内容。通过 Bash 运行,无需为文件增加执行权限。
#!/usr/bin/env bash
build() { printf 'build\n'; }
run_tests() { printf 'test\n'; return "${1:-0}"; }
deploy() { printf 'deploy\n'; }
build && run_tests "${1:-0}" && deploy
lesson_status=$?
if (( lesson_status != 0 )); then
printf 'workflow failed: status=%s\n' "$lesson_status" >&2
fi
exit "$lesson_status"
bash course15-flow.sh 0
echo $?
bash course15-flow.sh 1
echo $?
第一次输出 build、test、deploy,随后 echo $? 显示 0。参数改为 1 时,只运行到 build、test,标准错误输出失败消息,状态为 1。再用 0 运行即可回到成功流程。${1:-0} 表示第一个参数未提供或为空时使用 0;本练习仅输入 0 或 1。
name() { ...; } 为命令组定义函数,return 返回状态。示例立即把执行结果保存在 lesson_status,避免提示消息覆盖原始失败状态;最后 exit 返回它。在实际系统中替换为已准备、验证的任务,并明确处理每一步失败。
独立的短任务可用分号,依赖前一步成功的任务用 &&,分支和恢复较复杂时用 if 与函数。work; cleanup 会在普通工作失败后运行清理,但不能保证 Shell 提前退出或收到信号时仍会清理,这类需求应通过 trap 等机制设计。set -e 在条件和 AND/OR 列表等场景有例外,不能当作自动处理所有错误的开关。
别名在 Bash 读取命令时展开
alias 常用于在交互式 Bash 中定义简短名称。若已有 ll 别名,先检查并记录原定义,或换用新名字,避免覆盖自己的配置。输入下面一行后,在新的提示符下单独运行下一块命令。Bash 在执行前读取并解析命令,同一行或同一复合命令中的定义和使用可能无法按预期展开。
alias ll='ls -l'
type ll
ll
别名通常只影响当前 Shell,非交互式 Bash 默认不展开,除非另行启用相应选项。因此不要让自动化脚本意外依赖交互式别名。若本次新建了该别名,用 unalias ll 删除;若临时改了已有别名,则恢复记录的原定义。需要接收参数的可复用操作通常更适合 Shell 函数。
区分 Shell 变量与环境变量
Shell 变量可供当前 Shell 使用;通过 export 导出后,它也会进入此后启动的子进程环境。用练习专用名称,若该名称已有用途先换名:
LESSON_MODE=practice
printf '%s\n' "$LESSON_MODE"
export LESSON_MODE
printenv LESSON_MODE
两次输出均为 practice。第一次来自当前 Shell 的变量展开,第二次由外部 printenv 读取接收到的环境。导出不会反向改变父进程,也不会追溯修改已运行的其他 Shell。
只向一条外部命令提供值,可用 NAME=value command 的形式。练习结束在当前 Shell 执行 unset LESSON_MODE。不要把用户主目录或系统 PATH 等重要变量当作练习变量。
执行顺序不符合预期时检查什么?
- 不知道名称会运行什么时,先查
type -a name。 - 条件执行异常时,拆开各步骤,立即检查各自退出状态。
- 别名未生效时,确认是否为交互式 Bash,以及定义后是否在新命令行使用。
- 子进程读不到变量时,检查是否执行了
export。
顺序运算符、别名和变量都会影响命令执行,但不能互相替代。连接真正修改文件的命令之前,先用 true、false、printf 等副作用小的命令验证流程。
官方文档
有关列表的左结合、命令组、管道与 pipefail、别名展开、环境继承和退出状态的完整规则,可查看 man7 托管的上游 Bash(1) 手册中的 Lists、Compound Commands、Pipelines、ALIASES、ENVIRONMENT 和 EXIT STATUS 部分,并核对本机 Bash 版本。




