为什么我不能打印在env输出中看到的变量?


9

我对设置一个外壳实例与另一个外壳实例的环境变量感兴趣。所以我决定做一些研究。阅读了许多有关的问题后,我决定对其进行测试。

我生成了两个外壳A和B(PID 420),它们都在运行zsh。从外壳程序AI运行以下命令。

sudo gdb -p 420
(gdb) call setenv("FOO", "bar", 1)
(gdb) detach

从外壳B运行时,env我可以看到变量FOO确实设置为bar值。这使我认为FOO已在shell B的环境中成功初始化。但是,如果我尝试打印FOO,则会出现一个空行,表明未设置FOO。对我来说,这里似乎有矛盾。

这已在我自己的Arch GNU / Linux系统和Ubuntu VM上进行了测试。我还在bash变量甚至未在env中显示的地方对此进行了测试。尽管这让我感到失望,但如果shell在生成时缓存其环境的副本并仅使用它(在链接的问题之一中建议),则是有意义的。这仍然不能回答为什么zsh可以看到变量。

为什么输出为echo $FOO空?


编辑

输入评论后,我决定进行更多测试。结果可以在下表中看到。第一列是FOO注入变量的外壳。第一行包含该命令的输出,可以在其下面看到。FOO使用:注入变量sudo gdb -p 420 -batch -ex 'call setenv("FOO", "bar", 1)'。zsh:专用的命令zsh -c '...'也已使用bash进行了测试。结果是相同的,为简洁起见,省略了它们的输出。

Arch GNU / Linux,zsh 5.3.1,bash 4.4.12(1)

|      |  env | grep FOO  | echo $FOO |  zsh -c 'env | grep FOO'  |  zsh -c 'echo $FOO'  |         After export FOO          |
|------|------------------|-----------|---------------------------|----------------------|-----------------------------------|
| zsh  |  FOO=bar         |           | FOO=bar                   | bar                  | No Change                         |
| bash |                  | bar       |                           |                      | Value of FOO visible in all tests |

Ubuntu 16.04.2 LTS,zsh 5.1.1,bash 4.3.48(1)

|      |  env | grep FOO  | echo $FOO |  zsh -c 'env | grep FOO'  |  zsh -c 'echo $FOO'  |         After export FOO          |
|------|------------------|-----------|---------------------------|----------------------|-----------------------------------|
| zsh  |  FOO=bar         |           | FOO=bar                   | bar                  | No Change                         |
| bash |                  | bar       |                           |                      | Value of FOO visible in all tests |

以上似乎暗示结果与分布无关。这并不能告诉我更多,zsh并且可以bash不同地处理变量设置。此外,export FOO在这种情况下,取决于外壳,其行为也非常不同。希望这些测试可以使别人清楚一些。


如果您zsh -c 'echo $FOO'改为执行(使用单引号!)会发生什么?那你看得到吗?
user1934428

从新的子外壳打印正确的值(也已对bash child进行了测试)。显然,环境是持久的,因为孩子可以继承它,但是父母为什么不尊重它呢?
rlf

3
我也那么认为。我猜想外壳在某处有一个变量符号表,其中一些被标记为“已导出”,这意味着在打开子外壳时,它们将放置在子进程的环境中。最初(外壳启动时),将当时环境中的变量复制到符号表中(当然也作为“导出”变量)。更改环境时,不会注意到shell更新其符号表-但是子进程(如env)会看到修改后的环境。
user1934428

2
我在Ubuntu 16.04上使用zsh 5.1.1和bash 4.3.48(1)进行了测试,似乎zsh在GDB中为其设置环境变量不会使它作为shell变量可见,但会导致将其传递给子进程(如你观察到的),同时设置一个bash 让它作为一个shell变量可见但没有导致它要在子进程过去了!看起来zsh和bash使用不同的策略来管理变量,zsh跟踪非环境变量,并且bash将其所有内容存储在其环境中,并在启动(非subshel​​l)子级时对其进行了消毒。
伊利亚·卡根

@EliahKagan,有趣;您应该将其发布为答案。我也想知道它是否有区别,如果你运行export FOObash
通配符

Answers:


2

大多数弹不使用getenv()/ setenv()/ putenv()API。

启动时,它们为每个环境变量创建shell变量。这些将存储在内部结构中,该内部结构需要携带其他信息,例如变量是否已导出,只读……它们不能environ为此使用libc 。

同样,因为这个原因,他们不会使用execlp()execvp()执行命令,但拨打execve()直接系统调用,计算envp[]基于其输出变量的名单上排列。

因此,在您的中gdb,您需要向该shell内部变量表添加一个条目,或者可能调用正确的函数,该函数将使其解释export VAR=value代码以自行更新该表。

至于为什么你看到之间的差异bash,并zsh当你调用setenv()gdb,我怀疑那是因为你所呼叫setenv()的外壳初始化之前在进入,例如main()

您会注意到,bash's main()int main(int argc, char* argv[], char* envp[])(并bash从中映射那些env vars中的变量envp[]),而zsh's是int main(int argc, char* argv[])zsh从中获取变量environsetenv()确实会修改,environ但不能envp[]就地修改(在多个系统上为只读,以及这些指针指向的字符串)。

无论如何,environ在启动后读取外壳程序后,使用setenv()将无效,因为外壳程序之后将不再使用environ(或getenv())。

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.