为啥不能在 bash 4.1.2 中访问带有破折号的环境变量?

Posted

技术标签:

【中文标题】为啥不能在 bash 4.1.2 中访问带有破折号的环境变量?【英文标题】:Why can't environment variables with dashes be accessed in bash 4.1.2?为什么不能在 bash 4.1.2 中访问带有破折号的环境变量? 【发布时间】:2016-08-27 14:54:09 【问题描述】:

在 CentOS 5 主机(使用 bash 3.2.32)上,我们使用 Ruby (1.8.7) 来

ENV['AWS_foo-bar_ACCESS_KEY'] = xxxxx

然后,使用 bash,我们运行一个 shell 脚本:

BUCKET_NAME=$1
AWS_ACCESS_KEY_ID_VAR="AWS_$BUCKET_NAME_ACCESS_KEY_ID"
AWS_ACCESS_KEY_ID="$!AWS_ACCESS_KEY_ID_VAR"
export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID

这在 CentOS 5 上运行良好。

但是,在 CentOS 6(使用 bash 4.1.2)上,我们得到了错误

-bash: export: `AWS_foo-bar_ACCESS_KEY_ID=xxxxx': not a valid identifier

我们的理解是这会失败,因为变量名中不允许有-。但是为什么这适用于 bash 3.2 而不是 bash 4.1?

【问题讨论】:

重要的不是操作系统版本,而是bash的版本。 @Barmar 你能扩展一下吗? CentOS 5 机器正在运行 bash 3.2(我的 Mac 也是如此),而 CentOS 6 机器正在运行 bash 4.1。我在bash changelog 中没有看到任何表明在变量名称中放弃对- 的支持(或者如果这种行为原本不应该被允许,则修复错误)。 - 从未被允许在变量名中使用。它用作参数扩展语法的一部分:$foo-bar 表示返回 $foo 的值,但如果未设置变量,则默认为 "bar" 我怀疑旧版本在做变量间接时没有检测到这个问题,修复了这个bug。 简短形式:永远不要依赖未定义的行为。 【参考方案1】:

“为什么”几乎无关紧要:POSIX 标准非常清楚地表明,export 只需要支持有效名称的参数,任何带有破折号的东西都不是有效名称。因此,不需要 POSIX shell 来支持通过间接扩展或其他方式使用破折号导出或扩展变量名。

值得注意的是,ShellShock - 一个由对环境内容的草率处理引起的主要安全漏洞 - 在当前 CentOS 6 更新存储库中的 bash 4.1 中得到修复;在产生安全漏洞的领域增加严格性应该不足为奇。

此答案的其余部分将重点说明 bash 4.1 的新行为是 POSIX 明确允许甚至要求的,因此先前的行为是未定义的实现工件。


致quote POSIX on environment variables:

这些字符串的形式为 name=value;名称不得包含字符“=”。对于要在符合 IEEE Std 1003.1-2001 的系统之间可移植的值,该值应由可移植字符集中的字符组成(NUL 除外,如下所示)。环境中字符串的顺序没有任何意义。如果进程环境中有多个字符串具有相同的名称,则后果是不确定的。

IEEE Std 1003.1-2001 的 Shell 和 Utilities 卷中的实用程序使用的环境变量名称仅包含大写字母、数字和来自 Portable Character Set 中定义的字符的“_”(下划线)和不要以数字开头。 实现可能允许使用其他字符;应用程序应容忍此类名称的存在。大写和小写字母应保持其唯一身份,不得折叠在一起。 包含小写字母的环境变量名称的名称空间是为应用程序保留的。 应用程序可以使用此名称空间中的名称定义任何环境变量,而无需修改标准实用程序的行为。

注意:其他应用程序可能难以处理以数字开头的环境变量名称。因此,不建议在任何地方使用此类名称。

因此:

需要工具(包括 shell),以完全支持包含大小写字母、数字(第一个位置除外)和下划线的环境变量名称。 工具(包括外壳)可能会根据环境变量修改其行为,环境变量的名称符合上述规定且另外不包含小写字母。 工具(包括外壳)应该容忍其他名称——这意味着它们在它们面前不应该崩溃或行为不端——但不需要支持它们。

最后,shell 被明确允许丢弃不是 shell 变量名的环境变量名。来自the relevant standard:

未指定在调用时传递给 shell 的环境变量是否包含在传递给 execl() 和(如果 execl() 如上所述失败)到新的 shell。


此外,什么定义了有效的外壳名称is well-defined:

名称 - 在 shell 命令语言中,仅由可移植字符集中的下划线、数字和字母组成的单词。名称的第一个字符不是数字。

值得注意的是,在符合 POSIX 标准的 shell 中,只有下划线(不是破折号)被视为有效名称的一部分。


...和the POSIX specification for export 明确使用“名称”一词(它在上面引用的文本中定义),并将其描述为应用于“变量”(shell 变量,对其名称的限制也是主题本文档其他地方引用的限制):

shell 应将导出属性赋予与指定名称对应的变量,这将使它们处于随后执行的命令的环境中。如果变量名后跟=word,则该变量的值应设置为word。


以上所说的——如果你的操作系统提供了一个/proc/self/environ,它代表你的环境变量在进程启动时的状态(在shell之前,因为它被允许这样做,可能会丢弃任何不在 shell 中具有有效名称),您可以提取具有无效名称的内容,如下所示:

# using a lower-case name where possible is in line with POSIX guidelines, see above
aws_access_key_id_var="AWS_$BUCKET_NAME_ACCESS_KEY_ID"
while IFS= read -r -d '' var; do
  [[ $var = "$aws_access_key_id_var"=* ]] || continue
  val=$var#"$aws_access_key_id_var="
  break
done </proc/self/environ
echo "Extracted value: $val"

【讨论】:

感谢您的详细介绍。整个事情最让我惊讶的部分是,首先用 ruby​​ 设置变量是有效的。我不会想到 POSIX 会比 CentOS 更严格,或者 CentOS 会允许不常见的模式(以更准确者为准)。 @MatthewHerbst,这不是关于 CentOS-vs-POSIX,而是关于 POSIX-shell-spec 与 POSIX-environment-variable-spec;操作系统的其余部分遵循的规范比 shell 限制更少。 @MatthewHerbst, ... C 中的setenv() 调用至少允许“便携式字符集”中不是= 或NUL 的任何内容(请参阅此答案中引用的第一块规范,以及pubs.opengroup.org/onlinepubs/009696899/basedefs/… 用于所述集合的定义),因此您的非 shell 语言允许破折号 - 但 shell 的定义更严格地定义了“名称”的构成,并且明确允许丢弃不属于该规范的环境变量。 非常有趣,谢谢。出于好奇,你知道为什么决定 shell 会更严格吗? @MatthewHerbst,虽然这个决定是在 70 年代做出的——在我之前——最明显的答案是满足语法的需求(不一定是这个特定的语法,甚至是 任何 合理的语法)。例如,$" 是可移植字符集的成员(因此在操作系统级别的环境变量名称中有效),但是如果允许其中任何一个作为 bash 中变量名称的一部分,则需要更改语法几乎认不出来。因此,all-environment-variables-automatically-become-shell-variables 规则有效地创建了这个约束。

以上是关于为啥不能在 bash 4.1.2 中访问带有破折号的环境变量?的主要内容,如果未能解决你的问题,请参考以下文章

在 bash 中使用 :- (冒号破折号)

bash、破折号和字符串比较

为啥我不能在 bash 脚本中使用作业控制?

为啥我不能在 bash 脚本中使用作业控制?

为啥我不能访问指向数组中成员函数的指针?

为啥 ---(3 个破折号/连字符)在 yaml 文件中?