“子外壳”和“子进程”之间的确切区别是什么?


16

根据这个这个,一个子shell通过使用括号开始(…)

( echo "Hello" )

根据这个这个这个,当命令以&

echo "Hello" &

Posix规范在此页面中使用该词subshell但未定义该词并且在同一页面上也未定义“子进程”

两者都使用内核fork()功能,对吗?

那么,将某些forks称为“ sub-shell”,而将另一些forks称为“子进程”,则有什么区别呢?


不清楚为什么要链接POSIX 基本原理:基本定义而不是基本定义本身:3.93子进程 “由给定进程创建的新进程(由fork(),posix_spawn()或...)”3.376子 外壳“与主要或当前外壳执行环境区分开的外壳执行环境”。因此,不是同类事物的实例。这是您要寻找的区别吗?
fra-san

@ FRA-SAN中的child process可能有一个独特的环境比main:像( LANG=C eval 'echo "$LANG"' )。该子进程(在括号内)是否也是子shell(不同的环境)?
以撒

根据定义, in中的表达式( )是具有其自身执行环境的子shell。我的观点是,不需要将子外壳实现为子进程(正如Stéphane在其关于ksh93示例的回答中指出的那样)。看起来subshel​​l子进程不必都是调用的结果;因此,在我看来,寻找两种货叉之间的区别似乎并不正确。这就是为什么我试图更好地理解您的问题。fork()
fra

嗯,现在我看到您链接到的tldp页面实际上说一个子shell 一个子进程。我认为该定义可能是一种误导性的简化。
fra

Answers:


15

在POSIX术语中,子Shell环境与Shell Execution Environment的概念相关联。

子shell环境是作为父环境的副本创建的单独的shell执行环境。执行环境包括打开的文件,umask,工作目录,shell变量/函数/别名等内容。

对该子外壳环境的更改不会影响父环境。

传统上,在POSIX规范所基于的Bourne shell或ksh88中,这是通过派生子进程来完成的。

POSIX要求或允许命令在子shell环境中运行的区域是传统上ksh88派生子shell进程的区域。

但是,它不会强制实现为此使用子进程。

Shell可以选择以自己喜欢的任何方式来实现该单独的执行环境。

例如,ksh93通过保存父执行环境的属性并在子shell环境终止时在可以避免分叉的上下文中恢复属性来实现(因为在大多数系统上,分叉的优化非常昂贵)。

例如,在:

cd /foo; pwd
(cd /bar; pwd)
pwd

POSIX确实要求必须cd /foo在单独的环境中运行,并且输出类似以下内容:

/foo
/bar
/foo

它不需要在单独的进程中运行。例如,如果stdout变成断线,则pwd在subshel​​l环境中运行很可能会将SIGPIPE发送到唯一的shell进程。

包括在内的大多数shell bash都将通过评估(...)子进程内部的代码(而父进程等待其终止)来实现它,但是ksh93将改​​为在(...)同一进程中运行内部代码时:

  • 记住它是在subshel​​l环境中。
  • 在后cd,保存先前的工作目录(通常在使用O_CLOEXEC打开的文件描述符中),保存OLDPWD,PWD变量的值以及所有cd可能修改的内容,然后执行chdir("/bar")
  • 从子Shell返回后,将还原当前的工作目录(fchdir()在保存的fd中带有),并且子Shell可能已修改的所有其他内容。

在某些情况下,无法避免子进程。ksh93无法派生:

  • var=$(subshell)
  • (subshell)

但是在

  • { subshell; } &
  • { subshell; } | other command

也就是说,在这种情况下,事情必须在单独的进程中运行,以便它们可以并行运行。

ksh93优化远不止于此。例如,在

var=$(pwd)

大多数shell会派生一个进程,让子进程将pwd其stdout重定向到管道来运行命令,pwd将当前工作目录写入该管道,而父进程在管道的另一端读取结果ksh93,而不会虚拟化所有内容需要叉子或管子。叉子和管道将仅用于非内置命令。

请注意,除了子外壳,还有其他上下文,这些外壳为其子进程派生了子外壳。例如,要运行存储在单独的可执行文件中的命令(并且该脚本不是用于同一外壳解释器的脚本),外壳将必须派生一个进程来在其中运行该命令,否则它将不会能够在该命令返回后运行更多命令。

在:

/bin/echo "$((n += 1))"

那不是子shell,该命令将在当前shell执行环境中评估,当前shell执行环境的n变量将增加,但是shell将派生一个子进程以/bin/echo在其中执行$((n += 1))as 命令,并扩展as参数。

许多shell实现了一种优化,因为它们不是脚本或子shell的最后一个命令(对于那些作为子进程实现的子shell),它们不会派生一个子进程来运行该外部命令。(bash但是,仅当该命令是子shell的唯一命令时才这样做)。

这意味着,对于那些shell,如果子shell中的最后一个命令是外部命令,则该子shell不会导致产生额外的进程。如果您比较:

a=1; /bin/echo "$a"; a=2; /bin/echo "$a"

a=1; /bin/echo "$a"; (a=2; /bin/echo "$a")

将会创建相同数量的进程,只是在第二种情况下,第二个fork才更早完成,因此a=2可以在子shell环境中运行。


1

子壳

子外壳也称为子外壳。可以从父外壳和另一个外壳创建子外壳。可以使用以下方法创建子外壳:

1.流程清单

进程列表是用括号括起来的命令分组。例:

( pwd ; (echo $BASH_SUBSHELL)) 

这将打印当前的工作目录和生成的外壳号。注意调用subshel​​l很昂贵。

2.协同处理

它在后台模式下生成一个子shell,并在该子shell中执行命令。

coproc sleep 10

如果键入jobs命令

[1]+  Running                 coproc COPROC sleep 10 &

您将看到睡眠作为后台进程在后台运行。

分叉子进程

计算中的子进程是另一个进程创建的进程。每当执行外部命令时,都会创建一个子进程。此动作称为分叉。

$ps -f
UID        PID  PPID  C STIME TTY          TIME CMD  
umcr7     3647  3638  0 13:54 pts/0    00:00:00 bash
umcr7     3749  3647  0 13:59 pts/0    00:00:00 ps -f

就像ps -f外部命令一样(即外部命令,有时称为文件系统命令,是存在于bash shell外部的程序。)这将创建带有bash shell父ID的子进程。


0

两者(子壳和子壳)都是与父壳(都是父壳的孩子)不同的过程。也就是说,它们具有不同的PID。两者都以父外壳的fork(副本)开始。

子shell是父shell的副本,其中的变量,函数,标志和所有内容均与父shell一样可用。修改这些值不会影响父级。

子外壳程序以fork开头,但会重置为start configs给出的外壳程序默认值。它成为用于执行某些代码(shell或命令)的过程。

子shell可以访问变量值:

$ x=123; ( echo "$x")
123

子外壳程序不能(未导出的变量):

$ x=234; sh -c 'echo "x=$x"'
x=
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.