在编写一些代码时我发现这一行:
$ 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 中。它发生在 dash、ksh 中,但不会发生在 bash 或 zsh 中。
问的
问题是:
- 为什么 TZ 的值保留在当前的 shell 中?
- 如何避免/控制这种情况(如果可能)?
额外的。
我用这两行在几个 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。
答案1
正如您所发现的,这是规范的行为。但这也是有道理的。
该值保留在 shell 的环境中,其原因与当您将定义添加到其命令行时其他命令保留其他环境变量的值相同的原因 - 您正在其环境中设置变量。
这特殊的内置函数通常是任何 shell 中最本质的种类 -eval
本质上是 shell 解析器的可访问名称,set
跟踪和配置 shell 选项和 shell 参数,return
//触发循环控制流,break
处理信号,打开/关闭文件。这些都是基本的实用程序 - 通常是通过几乎没有包装纸来实现的。continue
trap
exec
执行大多数命令涉及一些分层环境 -子shell环境(不一定是一个单独的进程)- 当你调用特殊的内置函数时你不会得到它。因此,当您为这些命令之一设置环境时,您也为 shell 设置了环境。因为它们基本上代表了你的外壳。
但它们并不是唯一以这种方式保留环境的命令 - 函数也做同样的事情。对于特殊的内置命令,错误的行为有所不同 - 尝试cat <doesntexist
然后尝试exec <doesntexist
,甚至只是: <doesntexist
当cat
命令会抱怨时,exec
或:
会杀死 POSIX shell。命令行上的扩展错误也是如此。他们是主循环, 基本上。
这些命令不有为了保留环境 - 一些 shell 比其他 shell 更紧密地包裹其内部,暴露更少的核心功能,并在程序员和接口之间添加更多缓冲区。这些相同的 shell 也可能比其他 shell 慢一些。当然,它们需要大量的非标准调整才能使其符合规范。无论如何,这并不是一个坏的事物:
fn(){ bad_command || return=$some_value return; }
那东西是简单的。否则你怎么能如此简单地保留返回值bad_command
而不需要设置一堆额外的环境,但仍然有条件地进行作业?
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!
答案2
事实证明,这种行为有一个非常具体的原因。
对所发生情况的描述有点长。
只有作业。
(仅)由赋值组成的命令行将设置变量这壳。
$ unset a b c d
$ a=b c=d
$ echo "<$a::$c>"
<b::d>
分配的变量值将被保留。
外部命令。
外部命令设置变量之前的赋值那仅外壳:
$ 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 设置值。
- 用指定的变量赋值特殊的内置实用程序内置完成后仍然有效。
$ 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。
这不是通常使用的东西。
这意味着位于任何此特殊内置函数列表前面的赋值应在当前运行的 shell 中保留指定的值:
break : continue . eval exec exit export
readonly return set shift times trap unset
如果 shell 按照 POSIX 规范工作,就会发生这种情况。
结论:
可以仅为一个命令(任何命令)设置变量,方法是确保该命令不是一个特殊的内置。该命令command
是常规内置命令。它只告诉 shell 使用命令,而不是函数。此行适用于所有 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
: