用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
- /*******************************************************
- 使用fork(), exec(), dup2(), pipe() ,open()系统调用
- 完成与下列shell命令等价的功能。
- cmd1:grep -v usr /etc/passwd
- cmd2:wc -l
- cmd3:who
- grep -v usr /etc/passwd 2>&1 | wc -l > r.txt;who
- ********************************************************/
- #include <stdio.h>
- #include <fcntl.h>
- #include <unistd.h>
- int main()
- {
- int pfd[2];
- /* 建立管道 */
- pipe(pfd);
- if (fork()) /* parent */
- {
- if(!fork()) /* child 2 */
- {
- /* 将标准输出和标准错误重定向到管道写端口pfd[1] */
- dup2(pfd[1], 1);
- dup2(pfd[1], 2);
- close(pfd[1]);
- /* 关闭管道读端口pfd[0] */
- close(pfd[0]);
- /* 执行grep */
- execlp("/bin/grep", "grep", "-v", "usr","/etc/passwd", NULL);
- }
- else
- { wait(NULL);
- execlp("/usr/bin/who","who",NULL);
- /* 这里的who原本是ls,但是ls的结果不能进入stdout,不知为何。 */
- }
- }
- else /* child 1 */
- {
- /* 打开result.txt文件,若不存在该文件 */
- /* 则创建一个新文件并命名为r.txt */
- int f = open("r.txt", O_CREAT | O_TRUNC| O_WRONLY, 0666);
- /* 将标准输入重定向到管道读端口pfd[0] */
- dup2(pfd[0], 0);
- close(pfd[0]);
- /* 将标准输出重定向到r.txt */
- dup2(f, 1);
- close(f);
- /* 关闭管道写端口pfd[1] */
- close(pfd[1]);
- /* 执行wc */
- execlp("/usr/bin/wc", "wc", "-l", NULL);
- }
- }
为防止以后忘记,我把这张图[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);不就行了。
说白了,这个提示,懂的人不看也懂,不懂的人看了也不懂。
[1]收集的管道和重定向的一些区别-ChinaUnix博客 - IT人与你分享快乐生活
http://blog.chinaunix.net/space.php?uid=346158&do=blog&id=2131047
[2]VFS
http://learn.akae.cn/media/ch29s03.html
转载于:https://blog.51cto.com/hector/757013