为什么变量在子Shell中可见?


18

《学习狂书》提到子shell将仅继承环境变量和文件描述符等,并且不会继承未导出的变量:

$ var=15
$ (echo $var)
15
$ ./file # this file include the same command echo $var

$

据我所知,shell将为for ()和for 创建两个子shell ./file,但是为什么在()子shell中var虽然没有导出变量却标识了变量,而在./file没有标识的情况下为什么呢?

# Strace for () 
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f24558b1a10) = 25617
# Strace for ./file
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f24558b1a10) = 25631

我试图用来strace弄清楚这种情况是如何发生的,令人惊讶的是,我发现bash将对克隆系统调用使用相同的参数,因此这意味着分叉的进程()./file应该具有与父进程相同的进程地址空间,所以为什么在这种()情况下,子外壳可见该变量,而在相同./file情况下基于克隆系统调用,则不会发生这种情况?


vinc17说的是正确的,即使您拥有subshel​​l时也得到pstree,您也相信这件事。
PersianGulf

Answers:


15

The Bash学习手册是错误的。子外壳继承所有变量。$$保留偶数(原始外壳的PID)。原因是对于子shell,该shell只是分叉而不会执行新的shell(相反,当您键入时./file,将执行一个新命令,例如一个新的shell;在strace输出中,查看execve并类似) 。因此,基本上,它只是一个副本(具有一些已记录的差异)。

注意:这并非专门针对bash;对于任何shell都是如此。


好的,但是我现在尝试在shell上放下strace并尝试执行./file,但是找不到对exec的任何调用,因此两个进程的地址空间应该相同,因此如何解释呢?
user3718463

@ user3718463您是否也使用过跟踪孩子的-f选项strace?这是找到执行人员的必要条件。
vinc17

是的,我
想通

16

您或本书正在将子Shell与作为Shell的子进程混淆。

一些shell构造导致shell 分叉子进程。在Linux fork下,是更通用的clone系统调用的特例,您在strace日志中观察到了这种情况。子级运行一部分Shell脚本。子进程称为subshel​​l。最直接的这样的构造是command1 &command1在子shell中运行,随后的命令在父shell中运行。创建子外壳的其他构造包括命令替换$(command2)和管道command3 | command4command3在子command4外壳中运行,在大多数外壳中在子外壳中运行,但在ksh或zsh中不运行)。

子外壳程序是父进程的副本,因此它不仅具有相同的环境变量,而且还具有所有相同的内部定义:变量(包括$$原始外壳程序进程的进程ID),函数,别名,选项等。在执行子shell中的代码之前,bash将变量设置BASHPID为子进程的进程ID。

运行时./file,这将执行一个外部命令。首先,shell派生一个子进程;然后该子进程执行(通过execve系统调用)可执行文件./file。子进程继承其父进程的属性:环境,当前目录等。应用程序的内部方面在execve调用中丢失:未导出的变量,函数等是内核不知道的bash概念,并且当bash执行另一个程序时,它们会丢失。即使其他程序碰巧是bash脚本,它也会由不知道或不在乎其父进程也是bash实例的bash新实例执行。因此,shell变量(非导出变量)无法生存execve


这个答案为我清除了很多东西。我唯一不理解的是第二段中的这一句话:“孩子运行了一部分Shell脚本。” 指的是什么shell脚本?
flow2k

@ flow2k Shell正在解释的脚本(即程序)。
吉尔(Gilles)'所以
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.