导言
本节就shell子进程(subprocess)以及子SHELL(subshell)的基础概念和注意点加以介绍,配以实例,进行说明。将详细讨论如下问题:
-
subprocess和subshell是什么
-
subprocess的产生过程是什么
-
什么情况下会产生subprocess和subshell
-
shell编程中,subshell需要注意些什么
-
如何管理subprocess
subprocess和subshell是什么
子进程(subporcess)是从父子进程的概念出发的。unix操作系统的进程从init进程开始,经过不断fork-exec“繁衍”,形成了树状的父子进程结构。每个进程均有其对应的父进程(0进程不在讨论范畴内),就算是由于父进程先行结束导致的孤儿进程,也会被init(pid=1)领养,使其父进程ID为1。
子SHELL,顾名思义,就是由“当前shell进程”创建的一个子进程。因此,subshell概念是subprocess的子集,一个subshell一定是个subprocess。
subprocess的产生过程
事实上,所有进程的创建,都可视为子进程创建过程。unix操作系统进程的创建,基本可以归结为fork-exec的模式,即是:
-
通过fork创建子进程环境,
-
通过exec加载并执行进程代码。
在shell环境中,即是:
-
读取命令:读取用户由键盘输入的命令行;
-
解析:以命令名作为文件名,并将其它参数改造为系统调用execve( )内部处理所要求的形式;
-
fork:当前shell fork()出一个子进程(即子shell),此时该子shell是父shell的一个副本;
-
父shell等待:终端进程本身用系统调用wait4( )来等待子进程完成(如果是后台命令&,则不等待,立即返回1);
-
子shell exec():在subshell里,根据path指定的目录列表里的目录,找到外部命令command;以找到的命令command取代(exec)当前shell程序并执行,此时父shell等待subprocess工作完成;
-
命令完成后,向父进程(终端进程)报告,此时父进程醒来,在做必要的判别等工作后,回到(1),父shell再取下一条语句执行,或等待用户输入下一条命令;
图示如下:
什么情况下会产生subshell
&,提交后台作业
If a command is terminated by the control operator `&', the shell executes the command asynchronously in a subshell.
管道
Each command in a pipeline is executed in its own subshell
括号命令列表 ()操作符
Placing a list of commands between parentheses causes a subshell environment to be created
执行外部脚本、程序
When Bash finds such a file while searching the `$PATH' for a command, it spawns a subshell to execute it. In other words, executing filename ARGUMENTS is equivalent to executing bash filename ARGUMENTS
示例
1.shell在子shell中执行外部命令
比如在当前网络终端(通过ssh),键入ps -ef|grep work,看看进程树是如何的:
-
$ echo $$ #得到当前bash的pid 6010
-
$ pstree -n -a | less #查看进程树
-
-
init(1)-+-migration/0(2)
-
|-ksoftirqd/0(3)
-
...skipping...
-
|-sshd(2679)---sshd(5997)---sshd(6009)---bash(6010)-+-pstree(9378)
-
| `-less(9379)
从结果可以看到,所有进程由init派生而来,2679为sshd系统服务;5997和6009为sshd为work当前终端分配的一个session;6010为基于这个session的、为work用户提供交互操作的shell进程;由于包含管道命令,9378和9379为6010的子进程,即6010(bash)进程spawn了两次。
其中,当键入pstree | less 时,shell先分析pstree是否为非内建命令或别名,结果是外部命令,需要在子进程中执行之,故另启动一个进程(9378)去执行pstree命令;同样,less也按照上述方法执行。
2. 例1的延续
再来看下如果调用sh脚本,系统如何表现:
脚本示例:
-
#! /bin/bash
-
ping 127.0.0.1 | tail -f | grep time &> /dev/null
-
---------------------------------------
-
$ sh test.sh
-
$ pstree -n -a | less #在另一个terminal查看进程树
|-sshd(2679)-+-sshd(5139)---sshd(5143)---bash(5144)---sh(10252)-+-ping(10253)
| | |-tail(10254)
| | `-grep(10255)
---------------------------------------
root 5139 2679 0 08:52 ? 00:00:00 sshd: work [priv]
work 5143 5139 0 08:52 ? 00:00:00 sshd: work@pts/0
work 5144 5143 0 08:52 pts/0 00:00:00 -bash
work 10252 5144 0 09:07 pts/0 00:00:00 sh test.sh
work 10253 10252 0 09:07 pts/0 00:00:00 ping 127.0.0.1
work 10254 10252 0 09:07 pts/0 00:00:00 tail -f
work 10255 10252 0 09:07 pts/0 00:00:00 grep tome
可见,当sh test.sh的时候,当前bash6010另启动一个subshell(10252)去执行test.sh内的语句,之后的语句可视作把10252作为“执行test.sh的主干道”,ping命令作为10252的子进程(10253)执行,直到test.sh最后一句执行完毕,10252才完成他的工作,结束并返回给交互式bash6010。
在本体shell中执行脚本
有两个方法可以使shell在本体进程中执行脚本
1. source
一次性读取脚本内的所有shell语句,并依次顺序执行,不退出。
-
echo $$ #21502 终端shell进程
-
---------------------------------------
-
#!/bin/bash #script.sh
-
ping 127.0.0.1 | tail -f | grep time
-
---------------------------------------
-
source script.sh
-
---------------------------------------
-
ps -ef|grep work
-
#output
-
work 5404 5403 0 Dec07 pts/1 00:00:00 -bash
-
root 21497 2679 0 10:16 ? 00:00:00 sshd: work [priv]
-
work 21501 21497 0 10:16 ? 00:00:00 sshd: work@pts/2
-
work 21502 21501 0 10:16 pts/2 00:00:00 -bash
-
work 22735 21502 0 10:20 pts/2 00:00:00 ping 127.0.0.1
-
work 22736 21502 0 10:20 pts/2 00:00:00 tail -f
-
work 22737 21502 0 10:20 pts/2 00:00:00 grep time
可见,脚本里的命令都从21502(bash)中fork出来执行。
2. exec
相当于用"bash script"替换了本体进程,当在本地进程执行完毕后,已经没有shell程序了,退出。
-
#!/bin/bash
-
ping 127.0.0.1 -c 10
-
---------------------------------------
-
exec ./test.sh
-
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
-
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.010 ms
-
...
-
64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.005 ms
-
--- 127.0.0.1 ping statistics ---
-
10 packets transmitted, 10 received, 0% packet loss, time 8999ms
-
rtt min/avg/max/mdev = 0.004/0.006/0.010/0.002 ms, pipe 2
-
Connection to yf-adrc-ct00.yf01 closed.
可见,用exec执行的话,脚本结束即退出当前终端。
父子进程在编程中需要注意些什么
了解了进程fork的关系,就能明白为什么子进程不能改父进程的变量。
父子进程的运行环境、内存空间都是独立的,父进程的环境变量会传递到子进程里,子进程对任何变量的变更不会影响父进程。
-
$ echo $$ #父进程7182
-
7182
-
$ name=father #定义name变量
-
$ export name #使其环境变量
-
---------------------------------------
-
$ bash #产生一个子进程
-
$ echo $$ #子进程8303
-
8303
-
$ echo $name #子进程得到了name
-
father
-
$ name=son #变更name=son
-
$ exit #回到父进程
-
---------------------------------------
-
$ echo $name #父进程的name不变,还是"father"
-
$ father
结合子进程产生的条件,在变成中应该避免在子进程中企图改变父进程的变量值。
参考资料
http://blog.csdn.net/yjz0065/article/details/1190879
http://blog.csdn.net/sosodream/article/details/5683515
阅读(2430) | 评论(0) | 转发(0) |