我正在使用 x86_64 GNU/Linuxbash
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
system("set > setc"); // A subset of `$ set`
return 0;
}
我可以看到文件 setc 包含$ set
.
我很好奇外壳(父进程)如何决定向子进程提供什么以及不提供什么?
如果我想为子进程提供更多环境变量怎么办?怎样才能控制它呢?
答案1
shell 内置命令set
显示所有变量,而不仅仅是那些已导出到环境中的变量。
如果您想向环境添加变量,只需export variablename
在 shell 中执行即可。
答案2
在幕后,环境变量从一个程序传输到另一个程序的方式是通过execve
系统调用,从磁盘加载新的程序映像。 (该图像替换了当前程序;还有另一个系统调用,fork
,复制当前程序;函数(如system
组合fork
)execve
以及一些其他系统调用来启动子进程。)在这个低级别,程序可以将其想要的任何内容传递给后继程序。
然而,大多数程序只是传递它们启动时收到的环境。库函数例如system
读取初始化为程序初始环境的全局变量;这个全局变量可以被其他库函数修改,例如putenv
.如果程序不包含专门设计用于更改环境的代码,它会将其环境传输到其子进程。
在您的测试中,您使用了 shell 命令set
。此命令列出 shell 变量,它们是环境变量的超集。 shell将其所有环境变量公开为shell编程语言的变量;此外,您还可以定义更多变量。内置命令export
将 shell 变量的名称作为参数并将该变量标记为已导出;标记为导出的变量将添加到子流程的环境中。
在交互式 shell 中,通常有许多未导出的 shell 变量。新启动的非交互式 shell 的数量较少。这就是为什么您看到的变量system("set")
比在交互式 shell 中看到的变量少的原因。如果您查看环境变量(使用命令env
或export
不带参数),那么您会发现相同的变量(或者可能更多,具体取决于 shell)。
答案3
看完之后man bash
有了全貌。如果我在发布问题之前读过 man 就好了!顺便说一句,上面的一个答案似乎不正确,而另一个答案则未能澄清。所以:
shell 有一个执行环境,它由shell 在调用时继承的打开文件、文件创建模式掩码、从环境中的 shell 父级继承的 shell 参数等组成。(更多详细信息“man bash
命令执行环境”部分。)不带参数调用,吐出 shell 环境中存在的所有变量的值(以名称=值的形式)。 (请注意shell 环境的输出和组成部分。)declare
set
set
declare
现在,当要执行除内置或 shell 函数之外的简单命令时,会在分离执行环境,由 shell 的打开文件、文件创建模式掩码、标记为导出的 shell 变量和函数(shell 环境中的变量和函数的子集)等组成。如果我们尝试分叉此命令,则正如 Gilles 在上面的答案中所说的那样“如果程序不包含专门设计用于更改环境的代码,它会将其环境传输到其子进程。”
因此,当我们尝试执行上述程序时,该程序会接收 shell 环境的子集。然后,当我们说 system("set > setrc") 时,system() 创建一个子进程(顺便说一句,它是 shell),然后子 shell 执行set > setrc
。在执行此操作时,程序将其环境(父 shell 环境的简化版本)传输到子进程(shell 执行set
)。现在,当set > setrc
执行时,它会打印调用 shell 环境中存在的所有变量的值(以名称=值的形式),该环境是父 shell 环境的简化版本。这就是为什么减少了setrc的内容。
现在如何控制 Bash 传递给命令的环境:
我们可以借助 和 来控制 Bash 传递给命令的参数和export
函数declare -x
。 (man bash
环境部分。)
正如其他人所提到的export
,env
只是为了澄清:export
并且env
(不带参数)打印要传递给子进程(或命令)的环境,而不是 shell 的环境 whileset
并declare
吐出中的所有变量壳的环境。这就是为什么您可以 echo $VAR_NAME (任何存在于set
或declare
输出中的变量,尽管它不在env
或export
输出中)。