題目:設當前目錄下有三個二進制格式的可執行程序,文件名分別為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以及管道的一些知識。想了解更詳細的相關知識可以參考文章末尾的參考列表。
650) this.width=650;" border=0>
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);不就行了。
說白了,這個提示,懂的人不看也懂,不懂的人看了也不懂。