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 脚本的一部分。子进程称为clone
strace
子外壳。最直接的此类构造是command1 &
:command1
在子 shell 中运行,后续命令在父 shell 中运行。创建子 shell 的其他构造包括命令替换$(command2)
和管道command3 | command4
(command3
在子 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 都是如此。