Linux操作之输入输出重定向和管道

合集下载
相关主题
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/wait.h>
#define BUFFER 255
fprintf(stderr,"Pipe Error:%s\n\a",strerror(errno)); exit(1); }
if(fork()==0) {
close(fd[0]); printf("Child[%d] Write to pipe\n\a",getpid());
snprintf(buffer,BUFFER,"%s",argv[1]); write(fd[1],buffer,strlen(buffer));
close(pipe_out[1]);
// 关闭父进程的写管道的子进程写端
dup2(pipe_in[1], STDOUT_FILENO); // 复制父进程的读管道到子进程的标准输出
dup2(pipe_out[0], STDIN_FILENO); // 复制父进程的写管道到子进程的标准输入
close(pipe_in[1]); // 关闭已复制的读管道
// 关闭管道的子进程端
/* 现在可在 fd[0]中读写数据 */
shutdown(fd[0], SHUT_WR); // 通知对端数据发送完毕
/* 读取剩余数据 */
close(fd[0]);
// 关闭管道
/* 使用 wait 系列函数等待子进程退出并取得退出代码 */
}
很清楚,这比使用两个单向管道的方案要简洁不少。我将在此基础上作进一步的封装和改进。
printf("Parent[%d] Read:%s\n",getpid(),buffer); exit(1); }
Linux 上实现双向进程间通信管道
级别: 初级

吴咏炜 (adah@sh163.net), 开发者
2004 年 9 月 01 日
本文阐述了一个使用 socketpair 系统调用在 Linux 上实现双向进程通讯管道的方法,并提供了一个实
LINUX 还提供了< >; | <<等等重定向操作符.在这些过滤和重 定向程序当中,都用到了管 Linux 操作系统 C 语言编程入门 [24 of 104]
道这种特殊的文件.系统调用 pipe 可以创建一个管道. #include<unistd.h>; int pipe(int fildes[2]);
现。
问题和常见方法
Linux 提供了 popen 和 pclose 函数 (1),用于创建和关闭管道与另外一个进程进行通信。其接口如下:
FILE *popen(const char *command, const char *mode); int pclose(FILE *stream);
遗憾的是,popen 创建的管道只能是单向的 -- mode 只能是 "r" 或 "w" 而不能是某种组合--用户只能选 择要么往里写,要么从中读,而不能同时在一个管道中进行读写。实际应用中,经常会有同时进行读写的要求 望把文本数据送往 sort 工具排序后再取回结果。此时 popen 就无法用上了。我们需要寻找其它的解决方案。 有一种解决方案是使用 pipe 函数 (2)创建两个单向管道。没有错误检测的代码示意如下:
close(pipe_out[1]);
// 关闭写管道
/* 读取 pipe_in[0]中的剩余数据 */
close(pipe_in[0]); // 关闭读管道
/* 使用 wait 系列函数等待子进程退出并取得退出代码 */
}
当然,这样的代码的可读性(特别是加上错误处理代码之后)比较差,也不容易封装成类似于 popen/pclose 的 使用。究其原因,是 pipe 函数返回的一对文件描述符只能从第一个中读、第二个中写(至少对于 Linux 是如此 就只能采取这么累赘的两个 pipe 调用、两个文件描述符的形式了。
封装和实现 直接使用上面的方法,无论怎么看,至少也是丑陋和不方便的。程序的维护者想看到的是程序的逻辑,而不是 各样的繁琐细节。我们需要一个好的封装。 封装可以使用 C 或者 C++。此处,我按照 UNIX 的传统,提供一个类似于 POSIX 标准中 popen/pclose 函数调 最大程度的可用性。接口如下: FILE *dpopen(const char *command); int dpclose(FILE *stream); int dphalfclose(FILE *stream);
一个更好的方案 使用 pipe 就只能如此了。不过,Linux 实现了一个源自 BSD 的 socketpair 调用 (3),可以实现上述在同一个文 的功能(该调用目前也是 POSIX 规范的一部分 (4))。该系统调用能创建一对已连接的(UNIX 族)无名 soc 全可以把这一对 socket 当成 pipe 返回的文件描述符一样使用,唯一的区别就是这一对文件描述符中的任何一 这似乎可以是一个用来实现进程间通信管道的好方法。不过,要注意的是,为了解决我前面的提出的使用 sor 要关闭子进程的标准输入通知子进程数据已经发送完毕,而后从子进程的标准输出中读取数据直到遇到 EOF。 话每个管道可以单独关闭,因而不存在任何问题;而在使用双向管道时,如果不关闭管道就无法通知对端数据 闭了管道又无法从中读取结果数据。——这一问题不解决的话,使用 socketpair 的设想就变得毫无意义。 令人高兴的是,shutdown 调用 (5)可解决此问题。毕竟 socketpair 产生的文件描述符是一对 socket,socket 上 用,其中也包括 shutdown。——利用 shutdown,可以实现一个半关闭操作,通知对端本进程不再发送数据,
Linux 操作之输入输出重定向和管道
Unix 下使用标准输入 stdin 和标准输出 stdout,来表示每个命令的输入和输出,还使 用一个标准错误输出 stderr 用于输出错误信息。这三个标准输入输出系统缺省与控制终 端设备相联系在一起的。因此,在标准情况下,每个命令通常从它的控制终端中获取输 入,将输出打印到控制终端的屏幕上。
close(pipe_out[0]);
// 关闭已复制的写管道
/* 使用 exec 执行命令 */
} else {
// 父进程
close(pipe_in[1]); // 关闭读管道的写端
close(pipe_out[0]);
// 关闭写管道的读端
/* 现在可向 pipe_out[1]中写数据,并从 pipe_in[0]中读结果 */
• 当编译时在 gcc 中使用了"-pthread"命令行参数时,本实现会启用 POSIX 线程支持,使用互斥量保护对 实现可以安全地用于 POSIX 多线程环境之中。
• 与 popen 类似 (7),dpopen 会在 fork 产生的子进程中关闭以前用 dpopen 打开的管道。 • 如果传给 dpclose 的参数不是以前用 dpopen 返回的非 NULL 值,当前实现除返回-1 表示错误外,还会把
Unix 系统提供了一些特殊的设备文件,用在一些特殊情况下。例如一个特殊设备文 件为/dev/null,永远无法写满,写入的内容被系统立即丢弃。如果不想看到程序的输出, 可以使用它作输出。
$ make world > /dev/null 去除了屏幕输出,使整个程序执行过程非常平静。
管道文件 (转载 LINUX 下 C 入门)
int main(int argc,char **argv) {
char buffer[BUFFER+1]; int fd[2]; if(argc!=2) {
fprintf(stderr,"Usage:%s string\n\a",argv[0]); exit(1); }
if(pipe(fd)!=0) {
但是也可以重新定义程序的输入 stdin 和输出 stdout,将它们重新定向。最基本的用 法是将她们重新定义到一个文件上去,从一个文件获取输入,输出到另外的文件中等。
$ ls > ls.out
$ cat < ls.out
这种输入输出重定向带来了极大的灵活性,可以将输出结果记录下来,也可以将程 序所需要的输入使用文件提前准备就绪,这样一来多次执行就不需要重新输入。
printf("Child[%d] Quit\n\a",getpid()); exit(0); } else {
close(fd[1]); printf("Parent[%d] ຫໍສະໝຸດ Baiduead from pipe\n\a",getpid());
memset(buffer,'\0',BUFFER+1); read(fd[0],buffer,BUFFER);
件描述符接收来自对端的数据。没有错误检测的代码示意如下:
int fd[2];
pid_t pid;
socketpair(AF_UNIX, SOCKET_STREAM, 0, fd); // 创建管道
if ( (pid = fork()) == 0) { // 子进程
close(fd[0]);
// 关闭管道的父进程端
pipe 调用可以创建一个管道(通信缓冲区).当调用成功时,我们可以访问文件描述符 fild es[0],fildes[1].其中 fildes[0]是用来读的文件描述符,而 fildes[1]是用来写的文件描 述符. 在实际使用中我们是通过创建一个子进程,然后一个进程写,一个进程读来使用的
程序已修改调过,正确:
dup2(fd[1], STDOUT_FILENO);
// 复制管道的子进程端到标准输出
dup2(fd[1], STDIN_FILENO);
// 复制管道的子进程端到标准输入
close(fd[1]);
// 关闭已复制的读管道
/* 使用 exec 执行命令 */
} else {
// 父进程
close(fd[1]);
int pipe_in[2], pipe_out[2];
pid_t pid;
pipe(&pipe_in);
// 创建父进程中用于读取数据的管道
pipe(&pipe_out);
// 创建父进程中用于写入数据的管道
if ( (pid = fork()) == 0) { // 子进程
close(pipe_in[0]); // 关闭父进程的读管道的子进程读端
关于接口,以下几点需要注意一下:
• 与 pipe 函数类似,dpopen 返回的是文件结构的指针,而不是文件描述符。这意味着,我们可以直接使 文件缓冲区会缓存写入管道的数据(除非使用 setbuf 函数关闭文件缓冲区),要保证数据确实写入到 函数。
• 由于 dpopen 返回的是可读写的管道,所以 popen 的第二个表示读/写的参数不再需要。 • 在双向管道中我们需要通知对端写数据已经结束,此项操作由 dphalfclose 函数来完成。
具体的实现请直接查看程序源代码,其中有详细的注释和 doxygen 文档注释 (6)。我只略作几点说明:
• 本实现使用了一个链表来记录所有 dpopen 打开的文件指针和子进程 ID 的对应关系,因此,在同时用 多的时候,dpclose(需要搜索链表)的速度会稍慢一点。我认为在通常使用过程中这不会产生什么问 情况下这会是一个问题的话,可考虑更改 dpopen 的返回值类型和 dpclose 的传入参数类型(不太方便 或者使用哈希表/平衡树来代替目前使用的链表以加速查找(接口不变,但实现较复杂)。
$ echo “ today is “ > out
$ date >> out
使用 >>标记表示输出结果采用添加的方式,将结果附加在文件 out 后面,而不是简 单的将原有文件重新覆盖的方式。
更为灵活的方式是将输入输出和一个执行命令联系起来,而不是一个固定的文件。
$ ls -l | grep mbox 上面的命令,将 ls -l 的输入作为 grep 的输入,这种方式称为管道。Unix 提供了很 多功能强大的小命令,但使用管道将这些命令组合起来,就形成了非常强大的工具组合, 能完成非常复杂的工作。
相关文档
最新文档