Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2278373
  • 博文数量: 668
  • 博客积分: 10016
  • 博客等级: 上将
  • 技术积分: 8588
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-29 19:22
文章分类

全部博文(668)

文章存档

2011年(1)

2010年(2)

2009年(273)

2008年(392)

分类:

2009-02-25 13:50:21

1,stdin,stdout,stderr
对于新生成的任何进程来讲,都可以使用stdin,stdout,stderr这些文件指针来访问标准输入,标准输出,错误文件。他们的类型都是FILE *,属于c运行库的类型。而内核则使用文件描述符来代表文件。STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO分别被定义为0,1,2。
Dup2( srcfd , destfd )的作用为将srcfd文件描述符复制一份,并且让destfd代表复制后的文件描述符。这样srcfd,destfd指向共同的file table entry,并拥有共同的inode。调用该函数时,destfd除了可以是普通的文件描述符之外,还可以是STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO的任意一个。这就位管道命令提供了很好的支持。
注:调用dup2时,如果destfd已经是一个有效的文件描述符,那么将首先关闭destfd(调用close函数)。
2,fork
fork函数可以生成子进程,并且子进程共享父进程的所有的文件描述符,数据,heap,stack。Child其实是parent的一份clone。事实上linux就是利用clone实现的fork。
把fork和dup2,pipe结合起来,就可以实现shell中支持的管道|了。
[1] 父进程首先调用pipe,返回fd0(用来从管道读数据),fd1(用来象管道写数据)
[2] 父进程调用fork生成子进程,此时子进程也共享到了父进程的fd0,fd1
[3] 子进程调用dup2( fd0 , STDIN_FILENO),这样就导致了子进程从stdin读取数据时,实际是从管道读取数据
[4] 子进程调用exec系列函数,执行相应的程序
[5] 父进程通过fd1向管道写数据
[6] 子进程通过stdin读取父进程向管道写入的数据。
其实,这就是shell执行管道的基本流程。
3,|
一般的控制台程序都是从stdin读取参数或需要处理的数据,并把结果输出到stdout中。而unix的哲学是:
[1] Rule of Modularity: Write simple parts connected by clean interfaces
[2] Rule of Composition: Design programs to be connected to other programs
[3] Rule of Clarity: Clarity is better than cleverness
[4] Rule of Simplicity: Design for simplicity; add complexity only where you must
[5] Rule of Transparency: Design for visibility to make inspection and debugging easier
[6] Rule of Robustness: Robustness is the child of transparency and simplicity
[7] Rule of Least Surprise: In interface design, always do the least surprising thing
[8] Rule of Repair: When you must fail, fail noisily and as soon as possible
[9] Rule of Economy: Programmer time is expensive; conserve it in preference to machine time
[10] Rule of Generation: Avoid hand-hacking; write programs to write programs when you can
[11] Rule of Representation: Use smart data so program logic can be stupid and robust
[12] Rule of Separation: Separate policy from mechanism; separate interfaces from engines
[13] Rule of Optimization: Prototype before polishing. Get it working before you optimize it
[14] Rule of Diversity: Distrust all claims for “one true way”
[15] Rule of Extensibility: Design for the future, because it will be here sooner than you think
基于这些哲学,就导致了unix系统中有很多小的工具,每个工具的功能都很单一,并且这些工具可以任意组合,完成复杂的功能。比如find,grep,awk,xargs等等的组合。
这些功能组合是通过管道|来完成的,即前一个程序的输出,作为下一个程序的输入。
我们看看grep的命令行参数:
Usage: grep [OPTION]... PATTERN [FILE] ...
Search for PATTERN in each FILE or standard input.
Example: grep -i 'hello world' menu.h main.c
我们可以看到,grep可以从多个文件中搜索字符串,并且也可以从stdin读取字符串并搜索。上例中,就是从menu.h main.c中搜索hello world。如果输入grep –i ‘hello’,并回车,然后grep将等待用户输入一行数据,并在该行数据中搜索hello,如果找到的话,就会输出该字符串。
$grep –i ‘hello’
Abcdefg
Hi hello
Hi hello
Stop
^D
$
其中蓝色的字符为用户输入的字符,红色的为grep找到匹配的字符串后,输出的字符。
 
我们可以猜到grep的实现,如果命令行的最后有一个文件名,或多个文件名,或路径的话,将从这些文件中读取内容,并匹配regex。否则的话,将从stdin读取数据,并匹配regex。
联系到上面所讲的pipe,fork,dup2函数,我们可以看到,这就是shell的基本执行过程。需要注意的是,grep必须支持从stdin读取数据,否则管道就不能够实现了。当然,想支持管道操作的程序必须遵守这个规则。
4,Xargs
假设有这样一个需求,我们需要从整个文件系统中搜索字符串hello,我们写了如下的shell:
$grep –i ‘hello’  /*/*
这将导致命令行参数太多。而每个系统对于参数列表的大小都有限制。比如ARG_MAX一般至少定义为4096 bytes。如果超过了ARG_MAX,将产生shell错误:
Argument list too lang
为了避免这个问题可以使用xargs命令。他的格式为:
xargs [opt] [ command [initial-arguments] ]
其中opt是xargs本身的命令行参数。
他的作用为,build and execute command lines from standard input。他从stdin读取由空格分割的字符串(假设为arg0,arg1,… argN),并执行command [initial-arguments] arg0 arg1 …argN,如果参数太多的话,xargs保证参数大小在不超过系统限制的ARG_MAX bytes大小的前提下,一次或多次执行command [initial-arguments]命令。比如执行了如下命令:
$find / -name ‘*.h’ | xargs grep –i ‘stdin’ | less
假设执行两次,第一次为grep –i ‘stdin’ a1.h a2.h …  a3000.h | less
第二次为grep –i ‘stdin’ a3001.h a3002.h … a4000.h | less
该命令的实际执行情况为:(推测)
Shell执行findxargsless程序。xargs顺序执行了两次grep程序。
xargs从find的结果读取数据是很普通的,不需要额外的解释。
xargs同less的数据传递看起来有些麻烦,其实也挺简单的。xargsstdin读取管道的数据,并按照ARG_MAX为界限进行分割,执行forkexecv(“grep”)一次或多次就可以了。因为grep使用普通的printf来输出结果,而这样的结果正好作为less的输入。因为xargsgrep虽然存在父子关系,但是他们的stdout是同一个stdout。对于less程序来讲,grep的输出和xargs的输出是没有区别的。
 
注意:
[1] XXX | grep –i ‘hello’
[2] XXX | xargs grep –i ‘hello’
[1]的情况下,grep将通过管道读取XXXX的输出结果,并在该结果中搜索hello
[2]的情况下,xargs将通过管道读取XXXX的输出结果,并将该结果作为grep的最后的FILE参数,和grep –i ‘hello’组合成完整的命令(如 grep –i ‘hello’ stdio.h stdlib.h)后,执行该命令。grepstdio.h stdlib.h文件中搜索hello
两者对于管道的输出作不同的处理,前者是grep直接从stdin中读取管道数据,并搜索。后者是xargs(他的命令行选项为 grep –i ‘hello’直接从stdin中读取管道数据,并和grep –i ‘hello’组合,然后调用exec执行该命令。这种不同是体现在grepxargs对读取管道数据后采取的不同处理,和管道本身的机制没有关系。
阅读(2183) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~