首页 > 解决方案 > 为什么 `export` 会因替换错误而失败,但不会因命令失败而失败?

问题描述

众所周知,export在其变量 assignments 中掩盖了命令替换的返回值。但是,有趣的是, export它并没有掩盖替换失败的返回值:

$ (set -eu; export FOO="$(bad_command)"); echo $?
bash: bad_command: command not found
0
$ (set -eu; export FOO="${bad_variable}"); echo $?
bash: bad_variable: unbound variable
1
$ (set -eu; export FOO="${}"); echo $?  # bad substitution
bash: ${}: bad substitution
1

(类似的行为dash。)

规范的哪一部分表明命令替换失败不会通过 传播export,但参数扩展失败会传播?

man bash(GNU Bash 4.4)的相关部分:

set -u

执行参数扩展时,将未设置的变量和特殊参数“@”和“*”以外的参数视为错误。如果尝试对未设置的变量或参数进行扩展,shell 会打印一条错误消息,如果不是交互式的,则以非零状态退出。

export [-fn] [name[=word]] ...
export -p

提供names的标记为自动导出到随后执行的命令的环境。如果-f给出了选项,则名称指的是函数。如果没有给出名称,或者 -p提供了选项,则打印所有导出变量的名称列表。该-n选项会导致从每个name. 如果变量名后跟=word,则变量的值设置为wordexport返回退出状态 0,除非遇到无效选项,其中之一names不是有效的 shell 变量名,或者-f提供的name不是函数。

——我在这里看不到任何可以区分这两种情况的东西。特别是,export只是说变量的值“设置为” word,这表明它经历了正常的扩展过程(它确实如此)而没有特殊处理。

POSIX 规范参考:

标签: bashlanguage-lawyer

解决方案


这两种情况都不是真正的export

命令替换只是成为命令参数数组中的文本(空或非空)。Unix 进程模型没有任何机制来传递文本是否来自程序,或者该程序是否成功。

foo var="$(true)"这意味着当您运行vs foo var="$(false)"vsfoo var=""和 shell 内置命令时,不可能编写行为不同的外部命令,就像export传统上遵循相同的行为以便于实现一样。

使用set -u和未设置变量,该命令根本不会运行。如果在构建参数数组时遇到这种情况,shell 会简单地跳过执行,并报告失败。一个命令不能选择忽略这样的失败,因为它从来没有被咨询过。

如果在参数数组的构造过程中命令替换失败,当然可以实现一个新的 shell 模式,类似地跳过执行并报告失败,但这不是传统功能,因此不在 POSIX 规范中。


推荐阅读