用C实现的一个Bash脚本


题目:设当前目录下有三个二进制格式的可执行程序,文件名分别为cmd1,cmd2和cmd3.

(1).在Bash下的下列命令完成什么功能?

  ./cmd1 2>&1 | ./cmd2 > r.txt; ./cmd3

(2).编写C语言源程序完成与(1)相同的功能

提示:exec系统调用可用execlp("./cmd1","cmd1",(char *)0);

打开文件用 fd = open("r.txt", O_CREAT | O_TRUNC | O_WRONLY,0666);

dup2系统调用法为dup2(src_fd,dst_fd);

创建管道用pipe(int fds[2]);

等待子进程结束用wait(int staloc);

 

答:首先说明,0,1,2三个文件说明符分别代表stdin, stdout和stderr。

2>&1的意思是将cmd1的标准输出流和标准错误流合并到标准输出流中;

接下来通过管道将刚才的结果输出到cmd2的输入中;

cmd2>r.txt将cmd2的输出结果重定向到文件r.txt中;

最后是执行cmd3,这与前面的一切都没有关系。(有一点尚存疑问,那就是cmd3是否和cmd1一样,都将stdout和stderr合体了,由于我没有测试错误的用例,因此这里不是标答。)

(2)这题才是重点。首先说明管道和重定向的一些知识。

 

  【 a. 左边的命令应该有标准输出 | 右边的命令应该接受标准输入

      左边的命令应该有标准输出 > 右边只能是文件

      左边的命令应该需要标准输入 < 右边只能是文件

   b. 管道触发两个子进程执行"|"两边的程序;而重定向是在一个进程内执行。】[1]

做这道题之前我接触这方面比较少,下面这段代码参考了不少东西,但是不知来源,没法列举了。这段代码是在一个同学给我的源文件的基础上修改的,因为之前我基本上不会用管道 ( ̄. ̄)

为了能测试,我把虚无缥缈的三个cmd换成了活生生的grep,wc和who   ( ̄0  ̄)y

  1. /******************************************************* 
  2.  使用fork(), exec(), dup2(), pipe() ,open()系统调用 
  3.  完成与下列shell命令等价的功能。 
  4.     cmd1:grep -v usr /etc/passwd 
  5.     cmd2:wc -l 
  6.     cmd3:who 
  7.     grep -v usr /etc/passwd 2>&1 | wc -l > r.txt;who 
  8. ********************************************************/ 
  9. #include <stdio.h> 
  10. #include <fcntl.h> 
  11. #include <unistd.h> 
  12. int main() 
  13.     int pfd[2]; 
  14.  
  15.     /* 建立管道 */ 
  16.     pipe(pfd); 
  17.      
  18.     if (fork()) /* parent */ 
  19.     { 
  20.         if(!fork()) /* child 2 */ 
  21.         {    
  22.             /* 将标准输出和标准错误重定向到管道写端口pfd[1] */ 
  23.             dup2(pfd[1], 1); 
  24.             dup2(pfd[1], 2); 
  25.             close(pfd[1]); 
  26.          
  27.             /* 关闭管道读端口pfd[0] */ 
  28.             close(pfd[0]); 
  29.  
  30.             /* 执行grep */ 
  31.             execlp("/bin/grep""grep""-v""usr","/etc/passwd", NULL); 
  32.         } 
  33.         else 
  34.         {   wait(NULL); 
  35.             execlp("/usr/bin/who","who",NULL); 
  36.             /* 这里的who原本是ls,但是ls的结果不能进入stdout,不知为何。 */ 
  37.         } 
  38.     } 
  39.     else /* child 1 */ 
  40.     { 
  41.         /* 打开result.txt文件,若不存在该文件 */ 
  42.         /* 则创建一个新文件并命名为r.txt */ 
  43.         int f = open("r.txt", O_CREAT | O_TRUNC| O_WRONLY, 0666); 
  44.          
  45.         /* 将标准输入重定向到管道读端口pfd[0] */ 
  46.         dup2(pfd[0], 0); 
  47.         close(pfd[0]); 
  48.          
  49.         /* 将标准输出重定向到r.txt */ 
  50.         dup2(f, 1); 
  51.         close(f); 
  52.  
  53.         /* 关闭管道写端口pfd[1] */ 
  54.         close(pfd[1]); 
  55.         /* 执行wc */ 
  56.         execlp("/usr/bin/wc""wc""-l", NULL); 
  57.     } 

为防止以后忘记,我把这张图[2]放在下面,也为后来者提供一些方便,这张图很清晰地讲解了dup,dup2以及管道的一些知识。想了解更详细的相关知识可以参考文章末尾的参考列表。

 

 

FAQ:为啥要再开一个进程来跑grep -v usr /etc/passwd ?父进程不也可以执行吗?

A:啊蚂蚁!在执行了execlp("/bin/grep", "grep", "-v", "usr","/etc/passwd", NULL);后,程序就会exit(0)了,如果是父进程在执行,那么父进程就退出了,因此要在execlp执行完成后继续执行,就必须f**k一个子进程出来,用子进程来运行execlp,好让老爹活着继续生娃。其他地方也是一样的。

FAQ:这段程序有没有考虑僵尸进程活着孤儿进程的情况啊?

A:没有。。 ( ̄ε(# ̄)。。让它们去吧。。。僵尸和孤儿们就交给init老大照看了。。。

PS:必须吐槽一下这道题的提示。给出exec的用法还不如告诉execlp的原型;dup2的用法提示还是错的,或者说有误导作用,明明是将第二个参数重定向到第一个参数,结果它的参数的意思是从dst到src,我的英语一般,但是dst是destination,src是source,我还是知道的,提示起到了南辕北辙的作用;等待子进程结束就更加坑爹了,直接告诉做题者用wait(NULL);不就行了。

说白了,这个提示,懂的人不看也懂,不懂的人看了也不懂。

相关内容