既然你在谈论孙子,你显然是以级联的方式产生孩子。这是实现管道的一种可能方式。
但请记住,管道的返回值(echo $?
在终端中执行时得到的值)是从最右边的命令返回的值。
这意味着您需要在此级联实现中从右到左生成子级。您不想丢失该返回值。
现在假设为了简单起见我们只讨论内置命令(没有额外调用 fork() 和 execve()),一个有趣的事实是,在某些 shell 中,如“zsh”,最右边的命令不是甚至分叉。我们可以通过一个简单的管道命令看到这一点,例如:
export stack=OVERFLOW | export overflow=STACK
然后使用命令env
,我们可以欣赏环境变量中溢出=堆栈的持久性。它表明最右边的命令没有在子shell中执行,而在子shell中执行了export stack=OVERFLOW
。
注意:在像“sh”这样的 shell 中,情况并非如此。
现在让我们使用一个基本的管道命令来为这个级联实现提供一个可能的逻辑。
cat /dev/random | head
注意:即使cat /dev/random
据说是一个永无止境的命令,它也会在命令head
完成读取cat /dev/random
. 这是因为标准输入在head
完成后关闭,并且命令cat /dev/random
中止,因为它在损坏的管道中写入。
逻辑:
父进程(你的 shell)看到有一个管道要执行。然后它将分叉两个进程。父母留在你的外壳,它会等待孩子返回,并存储返回的值。
在第一代子的上下文中:(试图执行管道最右边的命令)它看到该命令不是最后一个命令,它会再次 fork() (我称之为“级联实现”)。现在 fork 已经完成,父进程将首先执行它的所有任务 ( head -1
),然后它会关闭它的标准输入和标准输出,然后等待它的子进程。这对于首先关闭标准输入和标准输出,然后调用 wait() 非常重要。如果在标准输入上读取,则关闭标准输出会将 EOF 发送到父级。关闭标准输入确保试图在管道中写入的孙子中止,并出现“管道损坏”错误。
在孙子的上下文中:它看到它是管道的最后一个命令,它只会执行命令并返回它的值(它关闭标准输入和标准输出)。