use*_*463 20 shell bash subshell
Learning Bash Book 提到一个子shell只会继承环境变量和文件描述符等,不会继承没有导出的变量:
$ var=15
$ (echo $var)
15
$ ./file # this file include the same command echo $var
$
Run Code Online (Sandbox Code Playgroud)
据我所知,shell 将为()和 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
Run Code Online (Sandbox Code Playgroud)
我试图strace弄清楚这是如何发生的,令人惊讶的是我发现 bash 将对克隆系统调用使用相同的参数,所以这意味着分叉进程()和./file应该具有与父进程相同的进程地址空间,那么为什么在这种()情况下,变量是否对子外壳可见,并且这种情况不会发生./file,尽管相同的参数基于克隆系统调用?
vin*_*c17 18
Learning Bash Book 是错误的。子外壳继承所有变量。即使$$(原始 shell 的 PID)被保留。原因是对于子shell,shell只是fork而不执行新的shell(相反,当您键入时./file,会执行一个新命令,例如一个新的shell;在strace输出中,查看execve和类似的) . 所以,基本上,它只是一个副本(有一些记录的差异)。
注意:这不是 bash 特有的;对于任何外壳都是如此。
Gil*_*il' 18
您或本书将子shell与作为shell的子进程混淆了。
某些 shell 构造导致 shell分叉子进程。在 Linuxfork下clone,这是您在strace日志中观察到的更一般系统调用的一个特例。子进程运行 shell 脚本的一部分。子进程称为子shell。最直接的这种结构是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。