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

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

Learning Bash Book提到子shell只会继承环境变量和文件描述符等,并且不会继承未导出的变量:

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

$

据我所知,shell 将创建两个子 shell for()和 for ./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应该具有与父进程相同的进程地址空间,那么为什么在这种()情况下,变量对子 shell 可见,而这种情况不会发生同样的./file情况,尽管相同的参数基于克隆系统调用?

答案1

您或本书将子 shell 与作为 shell 的子进程混淆了。

一些 shell 构造会产生 shell分叉一个子进程。在 Linux 下,这是您在日志中观察到的fork更通用系统调用的特例。子进程运行 shell 脚本的一部分。子进程称为clonestrace子外壳。最直接的此类构造是command1 &command1在子 shell 中运行,后续命令在父 shell 中运行。创建子 shell 的其他构造包括命令替换$(command2)和管道command3 | command4command3在子 shell 中运行,command4在大多数 shell 中在子 shell 中运行,但在 ksh 或 zsh 中则不然)。

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

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

答案2

《学习狂欢》这本书是错误的。子 shell 继承所有变量。甚至$$(原始shell的PID)也被保留。原因是,对于子 shell,shell 只是 fork 并不会执行新的 shell(相反,当您键入 时./file,会执行新命令,例如新的 shell;在 strace 输出中,查看execve类似) 。所以,基本上,它只是一个副本(有一些记录的差异)。

注意:这不是 bash 特有的;对于任何 shell 都是如此。

相关内容