变量分配影响当前正在运行的外壳


8

在编写一些代码时,我发现这一行:

$ TZ="America/Los_Angeles"       date; echo "$TZ"
Thu Dec 24 14:39:15 PST 2015

正确给出“洛杉矶”的实际时间,并且TZ不保留变量的值。一切都应该预期。

但是,使用此行(我以前用来扩展某些格式,并且基本上执行相同的操作)保留了TZ的值:

TZ="America/Los_Angeles" eval  date; echo "$TZ"
Thu Dec 24 14:41:34 PST 2015
America/Los_Angeles

经过多次测试后,我发现这仅在某些shell中发生。它发生在破折号,ksh中,但不发生在bash或zsh中。

Q的

问题是:

  • 为什么现在的外壳中保留TZ的值?
  • 如何避免/控制这种情况(如果可能)?

额外。

我使用以下两行在几个shell中运行测试:

myTZ="America/Los_Angeles"
unset TZ; { TZ="$myTZ"      date; } >/dev/null; echo -n "  direct $TZ"
unset TZ; { TZ="$myTZ" eval date; } >/dev/null; echo    "  evaled $TZ"

结果如下:

/bin/ash        :   direct   evaled America/Los_Angeles
/bin/dash       :   direct   evaled America/Los_Angeles
/bin/sh         :   direct   evaled America/Los_Angeles
/bin/bash       :   direct   evaled
/bin/ksh93      :   direct   evaled America/Los_Angeles
/bin/lksh       :   direct   evaled America/Los_Angeles
/bin/mksh       :   direct   evaled America/Los_Angeles
/bin/zsh        :   direct   evaled
/bin/zsh4       :   direct   evaled 

TZ值会影响除bash和zsh之外的所有shell中正在运行的shell。

Answers:


6

如您所知,这是规范行为。但这也是有道理的。

出于相同的原因,将值保留在外壳程序环境中的原因是,在将定义作为前缀添加到命令行之前,其他命令会保留其他环境变量的值-您正在其环境中设置变量。

特殊内建命令通常在任何壳的最内在的品种- eval本质上是shell的分析器的访问名称,set曲目并配置shell选项和参数外壳,return/ break/ continue触发回路控制流量,trap手柄信号,exec打开/关闭文件。这些都是基本的实用程序-通常仅用外壳上的肉和土豆上的包装纸来实现。

执行大多数命令涉及一些分层环境- 子外壳环境(不一定是一个单独的进程) -调用特殊内置函数时不会获得该环境。因此,当您为这些命令之一设置环境时,就为您的shell设置了环境。因为它们基本上代表了您的外壳。

但是,它们并不是唯一以这种方式保留环境的命令-函数也是如此。对于特殊的内置程序,错误的行为也有所不同-尝试再试cat <doesntexist一次exec <doesntexist,甚至只是: <doesntexistcat命令将抱怨时,execor :会杀死POSIX shell。命令行上的扩展错误也是如此。基本上,它们是主循环

这些命令不具有保留的环境-一些炮弹包裹的内部起来更紧密地比别人揭露的核心功能少,加上编程器和接口之间的多个缓冲。这些相同的外壳也可能会比其他外壳慢一些。无疑,它们需要进行很多非标准调整才能使其符合规格。不管怎样,这不是仿佛这是一个不好的事情:

fn(){ bad_command || return=$some_value return; }

那东西很容易。您又将如何bad_command简单地保留s的返回而不必设置大量额外的环境,而仍然有条件地进行分配?

arg=$1 shift; x=$y unset y

那种东西也可以。就地交换更简单。

IFS=+  set -- "$IFS" x y z
x="$*" IFS=$1 shift
echo "${x#"$IFS"}" "$*"

+x+y+z x y z

...要么...

expand(){
    PS4="$*" set -x "" "$PS4" 
    { $1; }  2>&1
    PS4=$2   set +x
}   2>/dev/null

x='echo kill my computer; $y'
y='haha! just kidding!' expand "${x##*[\`\(]*}"

是我喜欢使用的另一个

echo kill my computer; haha! just kidding!

@BinaryZebra-但要注意的是它们的工作原理不同-当您为其他命令设置变量时,它们将保留在该其他可执行文件的环境中。当您在外壳环境中设置变量时,它们也会持续存在。
mikeserv

3

事实证明,此行为有一个非常具体的原因。
关于发生的情况的描述会更长一些。

仅作业。

(仅)分配的命令行将为此 shell 设置变量。

$ unset a b c d
$ a=b c=d
$ echo "<$a::$c>"
<b::d>

分配的vars的值将保留。

外部命令。

外部命令之前的赋值仅设置 shell的变量:

$ unset a b c d
$ a=b c=d bash -c 'echo "one:|$c|"'; echo "two:<$c>"
one:|d|
two:<>

我的意思是“外部”是指必须在PATH中搜索的任何命令。

这也适用于普通的内置程序(例如cd):

$ unset a b c d; a=b c=d cd . ; echo "<$a::$c>"
<::>

到这里为止,一切都与通常预期的一样。

特殊内置。

但是对于特殊的内置插件,POSIX要求为此shell设置值

  1. 在内置程序完成后,使用特殊内置程序指定的变量分配仍然有效。
$ sh -c 'unset a b c d; a=b c=d export f=g ; echo "<$a::$c::$f>"'
<b::d::g>

我正在使用一个电话来sh假设它sh是POSIX兼容的外壳。

这不是通常使用的东西。

这意味着分配在任何特殊内置列表的前面的分配应将分配的值保留在当前运行的shell中:

break : continue . eval exec exit export 
readonly return set shift times trap unset

如果外壳按照POSIX规范运行,则会发生这种情况。

结论:

通过确保该命令不是特殊的内置命令,可以只为一个命令(任何命令)设置变量。该命令command是常规内置命令。它仅告诉外壳程序使用命令,而不使用函数。该行适用于所有shell(ksh93除外):

$ unset a b c d; a=b c=d command eval 'f=g'; echo "<$a::$c::$f>"
<::::g>

在这种情况下,将变量a和b设置为命令命令的环境,然后将其丢弃。

相反,这将保留分配的值(bash和zsh除外):

$ unset a b c d; a=b c=d eval 'f=g'; echo "<$a::$c::$f>"
<b::d::g>

请注意,在eval之后的分配是单引号,以防止不必要的扩展。

因此:要将变量放置在命令环境中,请使用command eval

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.