shell命令中变量赋值POSIX规范的基本原理

shell命令中变量赋值POSIX规范的基本原理,shell,Shell,我刚刚发现了我的一个shell脚本在像Ubuntu这样使用dash for/bin/sh的系统上的一个问题。我的脚本在执行二进制文件时需要传入一些环境变量,出于与此无关的原因,它使用eval运行二进制文件。对二进制文件使用“env”的精简版本如下: #!/bin/sh RUNBIN=env XYZ=abc eval $RUNBIN 使用dash,当运行env时,上面的代码无法将XYZ=abc传递到环境中。如果您再仔细研究一下,您还会发现它在脚本的其余部分将XYZ声明为(非导出的)shell变量

我刚刚发现了我的一个shell脚本在像Ubuntu这样使用dash for/bin/sh的系统上的一个问题。我的脚本在执行二进制文件时需要传入一些环境变量,出于与此无关的原因,它使用eval运行二进制文件。对二进制文件使用“env”的精简版本如下:

#!/bin/sh
RUNBIN=env
XYZ=abc eval $RUNBIN
使用dash,当运行env时,上面的代码无法将XYZ=abc传递到环境中。如果您再仔细研究一下,您还会发现它在脚本的其余部分将XYZ声明为(非导出的)shell变量。我想这是一个问题,并在其中提到:

如果[处理命令行]没有产生命令名,或者如果命令名是一个特殊的内置或函数,变量赋值将影响当前的执行环境。否则,应针对命令的执行环境导出变量赋值,并且变量赋值不得影响当前的执行环境,除非作为执行的扩展的副作用[在执行波浪线扩展和其他获取命令行的操作时]

问题实际上是关于变量赋值列表的处理,以及早期赋值是否在以后的赋值中可见,但措辞上的更改似乎产生了一个意外的副作用,即如果您运行的shell内置程序将执行某些操作,则使变量赋值完全无用。如果你做类似的事情,你也会遇到同样的问题

echo 'env | grep XYZ' > t
XYZ=abc . ./t
什么也不印
bash
实现了我期望的功能(在最后一个示例中,它打印XYZ=abc。或者在后续命令中,它使用
--posix
额外指定(但不导出)XYZ=abc。因此:

XYZ=abc . ./t
echo XYZ=$XYZ
打印两次
XYZ=abc

我觉得奇怪的是,当命令是一个内置命令时,变量分配会一直存在于后续命令中,但生活中充满了奇怪之处。然而,命令行上的变量分配没有导出到命令行运行的任何命令中,这似乎是完全错误的。不幸的是,
bash
,而我似乎在t方面是少数his-在我的Mac上,ksh和zsh做dash做的事情。使用
export
和括号来界定变量的范围,很容易解决这种行为,但不雅观。我的问题是,为什么有人想要POSIX行为,特别是通过提供在实践中有用的示例?还是应该将其报告为错误在POSIX中?

注意:shell对于命令行解释器来说是一个有点模糊的术语。每个shell(例如
sh
bash
ksh
dash
tcsh
)都可以自由地解释它所选择的内容。有些shell甚至根本没有类似于
sh
的语法[或语义]

然而,大多数Shell都遵循标准规则,因为它们往往最有意义。POSIX标准确实有意义,当我在下面尝试做一些分解时


如果[处理命令行]未产生命令名,则变量分配将影响当前执行环境

这包括以下内容:

XYZ=abc
echo $XYZ
XYZ=abc builtin
XYZ=def myfunction
XYZ
是一个简单的变量。它不设置导出的环境。这是人们所期望的


如果命令名是一个特殊的内置或函数,变量赋值将影响当前的执行环境

这包括以下内容:

XYZ=abc
echo $XYZ
XYZ=abc builtin
XYZ=def myfunction
它[有效地]是以下内容的简写:

XYZ=abc ; builtin
XYZ=def ; myfunction
env XYZ=abc external_program
原因是内置和/或函数在当前环境中运行,需要访问或可能修改其中的变量:

function myfunction ()
{
    XYZ=qrm$XYZ
}
但是…

bash
(例如)在默认情况下不会这样做[没有
--posix
]。要实现其默认行为,
bash
必须在内置/函数持续时间内“克隆”环境[此处为
XYZ
]的一部分],这也增加了实现的复杂性

POSIX选择的定义导致了一个更简单的实现。而且,大多数非bash shell都是以这种方式和bash以另一种方式进行的,这一事实可能会影响一些事情


否则,应针对命令的执行环境导出变量赋值,且不得影响当前的执行环境

这包括:

XYZ=abc external_program
原因是,这[实际上]是以下内容的简写:

XYZ=abc ; builtin
XYZ=def ; myfunction
env XYZ=abc external_program
通过在子对象中[在
fork
之后和
execvp
之前]设置
XYZ
,该行为很容易实现,因此不需要[以新机制的形式]额外的复杂性

旁注:如果要同时设置两种环境:

export XYZ=abc
external_program
myfunction
echo $XYZ

当我们执行
XYZ=abc
时,理想情况下,我们希望
XYZ
仅在命令执行期间保持不变(即,这是最有用的模型)

如前所述,对于外部系统来说,这很容易

但是,对于内部应用程序,唯一的方法是修改当前环境。否则,内置程序和函数无法正常工作。因此,在执行命令后,更改必须保持不变[异常:
bash
,如上所述]


因此,如果您使用的shell不完全遵循上述POSIX规则,那么您[可能]可以通过显式使用上述“速记”版本[或您需要的任何特殊shell特定序列]来保证想要的效果


就我个人而言,我根本不喜欢允许
XYZ=abc
。如果你想这样做,就去做
env XYZ=abc
XYZ=abc;myfunction
。在我看来,“复合”版本只是不必要的粗糙,需要一个人学习“另一件(次要的)事情”

这只是我的观点。显然,还有其他观点

POSIX必须平衡意见、已知的当前实现、缺乏规范清晰性等

bashlike4 "local X=3 ; local Y=4 ; local Z=5" ...

RUNBIN="env XYZ=abc external_program"
eval $RUNBIN
# (1) simple export -- parent gets XYZ
export XYZ=abc
eval $RUNBIN

# (2) export then unset -- parent temporarily gets XYZ
export XYZ=abc
eval $RUNBIN
unset XYZ

# (3) preserve, export, restore -- parent's original is preserved
SAVE_XYZ=$XYZ
export XYZ=abc
eval $RUNBIN
unset XYZ
XYZ=$SAVE_XYZ
unset SAVE_XYZ

# (4) similar to (3), but we've created some helper functions to streamline
preserve XYZ=abc QRM=jkl
eval $RUNBIN
restore XYZ QRM
export XYZ=abc
export QRM=jkl
external_program $*
RUNBIN="wrapper ..."
eval $RUNBIN