I/O重定向和管道——《Unix/Linux编程实践教程》读书笔记(第10章)
1、I/O重定向的概念与原因 及 标准输入、输出的标准错误的定义
所以的Unix I/O重定向都基于标准数据流的原理。三个数据了分别如下:
1)标准输入——需要处理的数据流
2)标准输出——结果数据流
3)标准错误输出——错误消息流
概念:所以的Unix工具都使用文件描述符0、1和2。标准输入文件的描述符是0,标准输出的文件描述符是1,而标准错误输出的文件描述符则是2。Unix假设文件描述符0、1、2已经被打开,可以分别进行读写操作。
通常通过shell命令行运行Unix系统工具时,stdin、stdout、stderr连接在终端上。因此,工具从键盘读取数据并且把输出和错误消息写到屏幕。
大部分的Unix工具处理从文件或标准输入读入的数据。如果在命令行上给出了文件名,工具将从文件读取数据。若无文件名,程序则从标准输入读取数据。从另一方面说,大多数程序并不接收输出文件名;它们总是将结果写到文件描述符1,并将错误消息写到文件描述符2。如果希望将进程的输出写道文件或另一个进程的输入去,就必须重定向相应的文件描述符。
重定向I/O的是shell而不是程序
最低可用文件描述符(lowest-available-fd)原则:文件描述符是一个数组的索引号。每个进程都有其打开的一组文件。这些打开的文件被保持在一个数组中。文件描述符即为某文件在此数组中的索引。当打开文件时,为此文件安排的描述符总是此数组中最低可用位置的索引。
将文件描述符0、1、2的概念和最低可用文件描述符原则结合使用,即可理解I/O重定向的工作原理
2、重定向标准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、1、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; }
3、管道编程
(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; }
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。