一、I/O重定向的概念與緣由 及 標準輸入、輸出的標準錯誤的定義 shell
因此的Unix I/O重定向都基於標準數據流的原理。三個數據了分別以下: 編程
1)標準輸入——須要處理的數據流 數組
2)標準輸出——結果數據流 緩存
3)標準錯誤輸出——錯誤消息流 ide
概念:因此的Unix工具都使用文件描述符0、1和2。標準輸入文件的描述符是0,標準輸出的文件描述符是1,而標準錯誤輸出的文件描述符則是2。Unix假設文件描述符0、一、2已經被打開,能夠分別進行讀寫操做。 工具
一般經過shell命令行運行Unix系統工具時,stdin、stdout、stderr鏈接在終端上。所以,工具從鍵盤讀取數據而且把輸出和錯誤消息寫到屏幕。 oop
大部分的Unix工具處理從文件或標準輸入讀入的數據。若是在命令行上給出了文件名,工具將從文件讀取數據。若無文件名,程序則從標準輸入讀取數據。從另外一方面說,大多數程序並不接收輸出文件名;它們老是將結果寫到文件描述符1,並將錯誤消息寫到文件描述符2。若是但願將進程的輸出寫道文件或另外一個進程的輸入去,就必須重定向相應的文件描述符。 ui
重定向I/O的是shell而不是程序 idea
最低可用文件描述符(lowest-available-fd)原則:文件描述符是一個數組的索引號。每一個進程都有其打開的一組文件。這些打開的文件被保持在一個數組中。文件描述符即爲某文件在此數組中的索引。當打開文件時,爲此文件安排的描述符老是此數組中最低可用位置的索引。 spa
將文件描述符0、一、2的概念和最低可用文件描述符原則結合使用,便可理解I/O重定向的工做原理
二、重定向標準I/O到文件
(1)如何將stdin定向到文件
a、close-then-open策略
close(0); int fd = open(file, O_RDONLY);
int oldfd = open(file, O_RDONLY); #ifndef DUP2 close(0); int newfd = dup(oldfd); #else int newfd = dup2(oldfd, 0); #endif close(oldfd);
man 2 dup
#include <unistd.h> int dup(int oldfd); int dup2(int oldfd, int newfd);
系統調用dup複製了文件描述符oldfd。而dup2將oldfd文件描述符複製給newfd。兩個文件描述符都指向同一個打開的文件。這兩個調用都返回新的文件描述符,若發生錯誤,則返回-1。
c、open-dup2-close策略
(2)重定向到文件
共有3個基本的概念,利用它們是的Unix下的程序能夠輕易地將標準輸入、輸出和錯誤信息輸出鏈接到文件:
a、標準輸入、輸出以及錯誤輸出分別對應於文件描述符0、一、2;
b、內核老是使用最低可用文件描述符;
c、文件描述符集合經過exec調用傳遞,且不會被改變。
例子:
/* * who_to_file.c * purpose: show how to redirect output for another program * idea: fork, then in the child, redirect output, then exec */ #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> int main(void) { pid_t pid; int fd; printf("about to run who into a file\n"); /* create a new process or quit */ if ((pid = fork()) == -1) { perror("fork"); exit(1); } /* child does the work */ if (pid == 0) { close(1); fd = creat("userlist", 0644); execlp("who", "who", NULL); perror("execlp"); exit(1); } /* parent waits then reports */ else { wait(NULL); printf("done running who. results in userlist\n"); } return 0; }
三、管道編程
(1)建立管道
man 2 pipe
#include <unistd.h> int pipe(int pipefd[2]);
系統調用pipe建立管道並將兩端鏈接到兩個文件描述符。pipefd[0]爲讀數據端的文件描述符,而pipefd[1]則爲寫數據端的文件描述符。
(2)技術細節:管道並不是文件
a、從管道中讀數據
管道讀取阻塞:當進程試圖從管道中讀數據時,進程被掛起直到數據被寫進管道。
管道的讀取結束標誌:當因此的寫操做關閉了管道的寫數據端時,試圖從管道讀取數據的調用返回0,意味着文件的結束。
多個讀操做可能會引發麻煩:管道是一個隊列。當進程從管道中讀取數據以後,數據已經不存在了。
b、向管道中寫數據
寫入數據阻塞直到管道有空間去容納新的數據
寫入必須保證一個最小的塊大小:POSIX標準規定內涵不會拆分小於512字節的塊。而Linux則保證管道中能夠存在4096字節的連續緩存。若是兩個進程向管道寫數據,而且沒一個進程都限制其消息不打因爲512字節,那麼這些消息都不會被內核拆分。
若無讀操做在讀數據,則寫操做執行失敗:若是因此的讀操做都已將管道的讀取端關閉,那麼對管道的寫入調用將會執行失敗。若是在這種狀況下,數據還能夠被接收的話,爲了不數據丟失,內核採用了兩種方法來通知進程:「此時的寫操做是無心義的」。首先,內核發送SIGPIPE消息給進程。若進程被終止,則無任何事情發生。不然write調用返回-1,而且將errno置爲EPIPE。
例子:
/* * pipe.c - * demonstrates how to create a pipeline from one process to another * takes two args, each a command, and connectes * argv[1]s output to input of argv[2] * usage: pipe command1 command2 * effect: command1 | command2 * limitations: commands do not take arguments * users execlp() since known number of args * note: exchange child and parent and watch fun */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define oops(m, x) { perror(m); exit(x); } int main(int argc, char **argv) { int thepipe[2], newfd, pid; if (argc != 3) { fprintf(stderr, "usage: ./pipe cmd1 cmd2\n"); exit(1); } if (pipe(thepipe) == -1) oops("cannot get a pipe", 1); if ((pid = fork()) == -1) oops("cannot fork", 2); if (pid > 0) { close(thepipe[1]); if (dup2(thepipe[0], 0) == -1) oops("could not redirect stdin", 3); close(thepipe[0]); execlp(argv[2], argv[2], NULL); oops(argv[2], 4); } close(thepipe[0]); if (dup2(thepipe[1], 1) == -1) oops("could not redirect stdout", 4); close(thepipe[1]); execlp(argv[1], argv[1], NULL); oops(argv[1], 5); return 0; }