Chinaunix

标题: 还是管道和子shell的问题,越来越糊涂了 [打印本页]

作者: lululau    时间: 2009-06-13 16:17
标题: 还是管道和子shell的问题,越来越糊涂了
shell在执行一个外部程序的时候,会首先fork一个子进程,然后exec,替换进程空间
经常看到论坛上有人说在使用管道的时候,当前shell会产生一个子shell来执行管道中的命令
这使我产生了一个疑问:单独执行两个外部命令cmda和cmdb(或者使用cmda; cmdb这种方式) 与
cmda | cmdb这两种执行方式是不是产生的进程个数是不一样的?

为了验证我的猜测,写了一个小程序,用来打印进程的PID和PPID:


  1. liuxiang@MacBookPro: ~/casecode/sh $ echo '#include <stdio.h>
  2. > #include <unistd.h>
  3. > int main(int argc, char *argv[]){
  4. >   printf("PID: %d\n", getpid());
  5. >   printf("PPID: %d\n", getppid());
  6. > }' > printppid.c
  7. liuxiang@MacBookPro: ~/casecode/sh $ gcc printppid.c -o printppid
复制代码


我使用的是bash,当前shell的PID是 1827:


  1. liuxiang@MacBookPro: ~/casecode/sh $ echo $SHELL
  2. /bin/bash
  3. liuxiang@MacBookPro: ~/casecode/sh $ echo $$
  4. 1827
复制代码


下面是执行printppid的结果,可以看到父进程是1827,也就是当前shell

  1. liuxiang@MacBookPro: ~/casecode/sh $ ./printppid
  2. PID: 2331
  3. PPID: 1827
复制代码


下面通过管道执行两次printppid,为使前一个printppid的输出也能打印出来,将其标准输出重定向到标准错误.
可以看到,管道左右两个进程的父进程都是1827,也就是说这个过程中,根本就没有什么子shell.
单独执行两个外部命令cmda和cmdb(或者使用cmda; cmdb这种方式) 与 cmda | cmdb这两种执行方式产生的进程个数也就应该是一样的

  1. liuxiang@MacBookPro: ~/casecode/sh $ ./printppid >&2 | ./printppid
  2. PID: 2332
  3. PPID: 1827
  4. PID: 2333
  5. PPID: 1827
复制代码


疑问好像可以得到答案了。
但是有一个问题却使我更迷糊了:

  1. liuxiang@MacBookPro: ~/casecode/sh $ var=1
  2. liuxiang@MacBookPro: ~/casecode/sh $ echo $var
  3. 1
  4. liuxiang@MacBookPro: ~/casecode/sh $ var=2 | ./printppid
  5. PID: 2356
  6. PPID: 1827
  7. liuxiang@MacBookPro: ~/casecode/sh $ echo $var
  8. 1
复制代码

为什么变量var的值没有变成2?
我想在使用管道的过程当中,对于shell内建命令以及shell赋值语句等
是不是shell也fork了一个子进程,然后没有exec,而是直接使用fork出来的这个子进程
执行了内建命令或者赋值语句?
所以我进一步验证,发现也不是我想的这么回事, 内建命令就是在当前的这个交互shell上执行的。

  1. liuxiang@MacBookPro: ~/casecode/sh $ eval 'echo $$' >&2 | ./printppid
  2. 1827
  3. PID: 2389
  4. PPID: 1827
复制代码


那么,哪位DX能给我解释解释为什么在经过var=2 | somecmd 之后,var在当前shell里的值没有变?
作者: ywlscpl    时间: 2009-06-13 16:37
那么,哪位DX能给我解释解释为什么在经过var=2 | somecmd 之后,var在当前shell里的值没有变?


引自13问
$ A=B C        # 空白鍵未被關掉,作為 IFS 處理。
        $ C: command not found.
        $ echo $A
        
        $ A="B C"        # 空白鍵已被關掉,僅作為空白鍵處理。
        $ echo $A
        B C

作者: ywlscpl    时间: 2009-06-13 16:46
参考下这个贴的讨论
http://bbs.chinaunix.net/viewthread.php?tid=1393874
作者: r2007    时间: 2009-06-13 16:48
虽然管道左边的var=2是个内置命令,但是我猜应该和普通命令一样都是在子shell中运行的。
作者: lululau    时间: 2009-06-13 16:48
标题: 回复 #2 ywlscpl 的帖子
A=B C  是将B赋给A,然后把A作为环境变量传给C,
那A=B | C呢?反正不是将A作为环境传给C的
作者: ywlscpl    时间: 2009-06-13 16:53
标题: 回复 #5 lululau 的帖子
我只是解释var=2未在当前shell生效这个问题啊?

加管道后是不会影响后面的子shell的

  1. [root@Mylinux ~]# cat mysh
  2. #!/bin/bash
  3. echo $a
  4. [root@Mylinux ~]# a="b" ./mysh
  5. b
  6. [root@Mylinux ~]# a="b" |./mysh

  7. [root@Mylinux ~]#
复制代码

作者: ly5066113    时间: 2009-06-13 16:56
标题: 回复 #1 lululau 的帖子
为什么 ./printppid >&2 | ./printppid 与 ./printppid 产生的进程个数不一样,这个我不懂,但你的实验确实证明了,他们都是在子shell中执行的,因为他们的PID都变了,而且PPID都是当前shell的PID。

子shell中的操作,是不会改变父shell的变量的。
作者: lululau    时间: 2009-06-13 17:06
标题: 回复 #7 ly5066113 的帖子
我理解的子shell应该指的是,比如有一个shell脚本,script.sh,这个脚本里执行若干命令:cmd1 cm2 ... 等等。
当你在./script.sh在执行这个脚本的时候,当前的交互shell会产生一个子shell来解释执行这个脚本,也就是说这个子shell
是cmdx的父进程(当然cmdx得是外部命令),而当前的交互shell是cmdx的爷爷进程
作者: lululau    时间: 2009-06-13 17:10
标题: 回复 #4 r2007 的帖子
不知道管道中赋值语句是不是在子进程中执行的
至少我知道管道中的eval(It's a shell builtin)不是在子进程中执行的:

eval 'echo $$ >&2' | eval 'echo $$'
作者: ly5066113    时间: 2009-06-13 17:28
标题: 回复 #8 lululau 的帖子
http://bbs.chinaunix.net/viewthread.php?tid=733138

subshell 的产生情况会有很多种,例如:
1、cmd1 | cmd2
2、(cmd)
3、`cmd`
。。。。。。
作者: lululau    时间: 2009-06-13 18:10
额。。。我知道怎么错的了
因为我根本就没搞明白什么是subshell
A subshell does not create a new instance of the shell, just a new environment.
我把subshell等同于一个新的shell进程实例了。。。
谢谢DX们~
作者: blackold    时间: 2009-06-13 23:40

作者: blackold    时间: 2009-06-14 00:16
cmda|cmdb的两个命令确实是分别在不同的subshell中执行。
但PID变了并且PPID为当前shell的PID, 这应该还不足以说明就是subshell中执行的。
比如
$ cmda;cmdb
也满足这个条件。

[ 本帖最后由 blackold 于 2009-6-14 00:17 编辑 ]
作者: ly5066113    时间: 2009-06-14 09:40
标题: 回复 #13 blackold 的帖子
受教
作者: haimming    时间: 2009-06-14 10:05
原帖由 blackold 于 2009-6-14 00:16 发表
cmda|cmdb的两个命令确实是分别在不同的subshell中执行。
但PID变了并且PPID为当前shell的PID, 这应该还不足以说明就是subshell中执行的。
比如
$ cmda;cmdb
也满足这个条件。

和我那天问的问题差不多,
PID和PPID是不是不能证明subshell的?
作者: blackold    时间: 2009-06-14 10:18
标题: 回复 #14 ly5066113 的帖子
Tim,折杀我了。

我觉得要先弄清楚sub shell的定义。

查了些资料,发现subshell的定义有些混乱。
bashMan:

Each command in a pipeline is executed as a separate process (i.e., in a subshell).  

When a simple command other than a builtin or shell function is to be executed, it is invoked in a
separate execution environment that consists of the following. Unless otherwise noted, the values are
inherited from the shell.


这个separate execution是subshell吗?

A. 在当前shell执行外部命令,如shell> date, 是fork+exec, 算不是subshell?
B. ()是fork一个child shell,该child再fork+exec来执行命令。这个subshell和A中的"subshell"显然是不同的。

UNIX: The Textbook, by Syed Mansoor Sarvar, Robert Koretsky and Syed Aqeel Sarvar中提到:
A child shell is also called subshell

问题是fork+exec是fork一个child shell,然后在该child shell中exec.
而执行脚本(shell>scriptname)时,是fort一个child shell A,该child shell A再fork一个child shell B, 在B中再exec.

那么child shell是指哪种情况?



UNIX at Fermilab中的Important UNIX Concepts:

When you give the shell a command associated with a compiled executable or shell script, the shell
creates, or forks, a new process called a subshell.

外部命令也在subshell中执行。

To execute most built-in commands, the shell forks a subshell which executes the command directly (no
exec system call). For the built-in commands cd, set, alias and source, the current shell executes the
command; no subshell is forked.


shell> built-inCommands这样执行时,大部分内部命令也是在subshell中执行。
可见,UNIX at Fermilab认为fork 一个child shell就是subshell, 不管是fork-fork+exec, 还是 fork+exec。

[ 本帖最后由 blackold 于 2009-6-14 10:27 编辑 ]
作者: blackold    时间: 2009-06-14 11:30
标题: 回复 #15 haimming 的帖子
要看如何理解什么是subshell。
作者: haimming    时间: 2009-06-14 11:34
标题: 回复 #17 blackold 的帖子
我刚才又去翻了下ABS,定义与我的理解不一样
A subshell is a separate instance of the command processor -- the shell that gives you the prompt at the
console or in an xterm window. Just as your commands are interpreted at the command line prompt, similarly
does a script batch-process a list of commands. Each shell script running is, in effect, a subprocess (child
process) of the parent shell.

作者: blackold    时间: 2009-06-14 11:53
标题: 回复 #18 haimming 的帖子
嗯,严格的subshell应该是指这种“定义”吧。

你理解的subshell是怎么样的?说来听听,让我学习学习。
作者: lululau    时间: 2009-06-14 11:59
我觉得把subshell理解成一个新的独立的环境表就可以解决我的疑问了
而产生subshell的过程中,根本就没有fork过
作者: blackold    时间: 2009-06-14 12:10
标题: 回复 #20 lululau 的帖子
这种理解应该是错的。

subshell肯定是fork的。
作者: blackold    时间: 2009-06-14 13:34
参考贴子:

[woodie等兄惠存]关于bash的管道与进程的几个知识点
http://bbs.chinaunix.net/viewthr ... p;extra=&page=1

    A=B  echo  $A
    echo  $A

为什么输出不是B?
http://bbs.chinaunix.net/viewthr ... p;extra=&page=2

(cmd) 为什么没有在subshell中运行?
http://bbs.chinaunix.net/viewthr ... p;extra=&page=1
作者: haimming    时间: 2009-06-14 15:17
标题: 回复 #17 blackold 的帖子
一开始以为是这样的

  1.              环境变量
  2.       shell--------->subshell------->cmd----+
  3.         ^                                        |
  4.         |                    结果显示             |
  5.        +---------------------------------+
复制代码

但实际上似乎并没有subshell这个东西的存在
画图不容易,老对不齐

[ 本帖最后由 haimming 于 2009-6-14 15:24 编辑 ]
作者: blackold    时间: 2009-06-14 21:15
标题: 回复 #23 haimming 的帖子
有啊。参见相关贴子。
作者: waker    时间: 2009-06-15 09:09
产生一个subshell就fork一个子进程嘛,你们在讨论什么?
作者: blackold    时间: 2009-06-15 09:22
waker,在讨论关于进程和subshell啊。

fork-exec(shellPrompt>external_cm)和fork-fork-exec(shellPrompt>./script)都可以理解为subshell,是这样吗?
作者: waker    时间: 2009-06-15 09:32
标题: 回复 #26 blackold 的帖子
./script也是external_cmd的一种啊,都是fork-exec,至于external-cmd再作什么动作完全是external-cmd的事
作者: blackold    时间: 2009-06-15 09:39
标题: 回复 #27 waker 的帖子
都是外部命令。

我的意思是一个二进制代码,另一个是shell脚本,二者的执行过程有所不同。

按照这个理解,外部命令都是在subshell中执行了。
作者: blackold    时间: 2009-06-15 09:43
原帖由 waker 于 2009-6-15 09:32 发表
都是fork-exec

谢谢waker指正!
作者: lululau    时间: 2009-06-15 09:46
标题: 回复 #28 blackold 的帖子
如果这样说的话,那么

shellPrompt>external_cmd

shellPrompt>(external_cmd)
又有什么不同呢
作者: waker    时间: 2009-06-15 09:51
原帖由 lululau 于 2009-6-15 09:46 发表
如果这样说的话,那么

shellPrompt>external_cmd

shellPrompt>(external_cmd)
又有什么不同呢

后面是显式的,前者是隐式的,或者你说后者是脱裤子放屁也可以,前面有个链接讨论过了吧?
作者: blackold    时间: 2009-06-15 10:02
标题: 回复 #30 lululau 的帖子
单个外部命令没有区别,多个就有区别了。

可以自己做实验验证。
作者: lululau    时间: 2009-06-15 10:03
标题: 回复 #31 waker 的帖子
那为什么 shellPrompt>var=value; external_cmd 之后,var 这个变量留在了当前shell进程的环境表里了;
而 shellPrompt>(var=value; external_cmd) 之后,var 这个变量既不存在于当前shell进程的环境里,也不存在于 external_cmd 进程的环境里?那var到底跑到哪里去了呢?在 (var=value; external_cmd) 执行的过程当中还产生过其他的进程吗?还是说这个var被放在了当前shell进程的一个临时的环境表里面了?
作者: blackold    时间: 2009-06-15 10:13
标题: 回复 #33 lululau 的帖子
多个命令的情况是不同的。

(var;cmd)是作为一个进程组执行的,会产生一个执行它们的“subshell",var影响这个“subshell"环境。
作者: lululau    时间: 2009-06-15 10:24
标题: 回复 #34 blackold 的帖子
恩,谢谢,试了,多个命令和单个命令确实不一样
好像多个命令的时候是:

                       fork                                     fork and exec
当前shell进程------------>subshell进程--------------------------->external_cmd进程

而单个命令的时候是:

                          fork                            exec
当前shell进程----------------->subshell------------>exetenal_cmd
                                              |_________________|
                                                               |
                                                          同一进程

这样理解对吧?
作者: blackold    时间: 2009-06-15 10:27
标题: 回复 #35 lululau 的帖子
基本上对了,不全对。

参考waker说的,"都是fork+exec".

fork, exec是in tandem的。
作者: waker    时间: 2009-06-15 10:38
标题: 回复 #33 lululau 的帖子
有一点再提醒你一下,普通的变量是不会放在环境表里的,环境表里放的是环境变量
比如export var中的var
或 LANG=C man man中的LANG
作者: haimming    时间: 2009-06-15 20:55
有哪本书专说这东东的?
作者: lululau    时间: 2009-06-15 23:02
看了waker和黑哥两位DX的讲解,我好像有点明白了
我画几张图来表达一下我现在的理解
还劳烦各位DX看看我现在的这个理解对不对。






另外我觉得,管道:cmd1 | cmd2 | cmd3
这种情况和圆括号内的命令组又不太一样
所有进程都是主Shell进程的子进程
包括执行内部命令逻辑的那个进程也是主进程的子进程
这里不存在孙子进程。

另外,a=$(external_cmd)的执行过程中,external_cmd进程是主Shell进程的子进程
a=$(external_cmd >&2)的执行过程中,external_cmd进程是主Shell进程的孙子进程
而,(external_cmd)和(external_cmd >&2)两种情况之间没有这个差别。

还有,$$和$PPID貌似不是环境变量,用env命令和不带参数的export命令,看不到这两个变量

[ 本帖最后由 lululau 于 2009-6-15 23:05 编辑 ]
作者: haimming    时间: 2009-06-15 23:28
为什么我用pstree一没找到过subshell?
作者: blackold    时间: 2009-06-16 08:39
标题: 回复 #39 lululau 的帖子
很漂亮!
这是用什么工具画的?
作者: lululau    时间: 2009-06-16 10:16
标题: 回复 #41 blackold 的帖子
呵呵,Mac OS X的一个字处理软件pages,然后屏幕截图
作者: beauty2003    时间: 2009-06-16 11:25
原帖由 lululau 于 2009-6-15 23:02 发表
看了waker和黑哥两位DX的讲解,我好像有点明白了
我画几张图来表达一下我现在的理解
还劳烦各位DX看看我现在的这个理解对不对。
336032
336033
336034
336035
336036

另外我觉得,管道:cmd1 | cmd2  ...



画得很专业!~我只会用visio
作者: 網中人    时间: 2009-06-16 14:35
抱歉,還沒仔細看這串討論。
不過要提醒一下:
sub shell 也有分 sub shell 跟 nested sub shell 。
作者: lululau    时间: 2009-06-16 15:15
标题: 回复 #44 網中人 的帖子
NetMan DX,其实我不知道subshell是怎样定义的
不知道哪里可以找到subshell的准确定义?
我上面画得图表达了我对不同的命令组合执行过程中的进程关系的理解
DX要是有空,给我指点指点,我这样理解有没有错
作者: haimming    时间: 2009-06-16 18:12
标题: 回复 #45 lululau 的帖子
ABS中的定义不是不准确的?我前面贴的是ABS中的,黑哥也贴了,不知是哪儿的
作者: blackold    时间: 2009-06-16 18:15
标题: 回复 #46 haimming 的帖子
我也没有看到明确的定义,只是根据上下文来理解。

netman所说的subshell和nested subshell应该是指上述两种不同subshell。
作者: blackold    时间: 2009-06-17 08:59
note:
sub shell a child shell executed under an other shell

作者: 飞雪横天    时间: 2009-06-19 08:58
原帖由 lululau 于 2009-6-15 23:02 发表
看了waker和黑哥两位DX的讲解,我好像有点明白了
我画几张图来表达一下我现在的理解
还劳烦各位DX看看我现在的这个理解对不对。
336032
336033
336034
336035
336036

另外我觉得,管道:cmd1 | cmd2  ...



lululau兄的解释正确么?




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2