linux i/o重定向与管道编程

1.Linux 使用三种流:

0:stdin           标准输入

1:stdout        标准输出

2:stderr         标准错误输出


2.默认的连接是tty

如果输入sort,按回车键,终端将会连接到sort工具上。随便输入几行文字,当按下Ctrl-D来结束文字输入的时候,sort程序对输入进行排序并将结果写到stdout.

3.重要概念

(1).shell并不将重定向标记和文件名传递给程序。

(2).重定向可以出现在命令行中的任何地方,且不需要空格来区分。

(3).shell提供对重定向向其他文件描述符的支持。例如,2>filename即将重定向描述符2,也就是将标准错误输出到给定的文件中。

(4).是shell,而非程序将输入和输出重定向的。


// io1.c
// 
#include<stdio.h>
int main(int ac,char *av[]){
    int i;
    printf("number of args:%d\n",ac);
    printf("args are:\n");
    for(i=0;i<ac;i++)
        printf("args[%d]=%s\n",i,av[i]);
    fprintf(stderr,"This message is sent to stderr.\n");
    return 0;
}

[war@war io1]$ ./io1 a b es sad  ddd
Number of args:6
args are:
args[0]=./io1
args[1]=a
args[2]=b
args[3]=es
args[4]=sad
args[5]=ddd
This Message is sent to stderr.


[war@war io1]$ ./io1 a b c d ef g > test
This Message is sent to stderr.
[war@war io1]$ cat test
Number of args:7
args are:
args[0]=./io1
args[1]=a
args[2]=b
args[3]=c
args[4]=d
args[5]=ef
args[6]=g

[war@war io1]$ ./io1 a b c d e f g >xyz one two 2>opps
[war@war io1]$ ls
io1  io1.c  io1.o  makefile  opps  test  xyz
[war@war io1]$ cat xyz
Number of args:10
args are:
args[0]=./io1
args[1]=a
args[2]=b
args[3]=c
args[4]=d
args[5]=e
args[6]=f
args[7]=g
args[8]=one
args[9]=two
[war@war io1]$ cat opps
This Message is sent to stderr.

一.将stdin定向到文件

1.close-then-open

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>

int main(){
	int fd;
	char line[100];

	fgets(line,100,stdin); printf("%s\n",line);
	fgets(line,100,stdin); printf("%s\n",line);
	fgets(line,100,stdin); printf("%s\n",line);

	close(0);                          // 关闭标准输入流
	fd = open("/etc/passwd",O_RDONLY); // 打开文件,重定向
	if(fd != 0){
		fprintf(stderr,"Cound not open data as fd 0\n");
		exit(1);
	}

	fgets(line,100,stdin); printf("%s\n",line);
	fgets(line,100,stdin); printf("%s\n",line);
	fgets(line,100,stdin); printf("%s\n",line);
	fgets(line,100,stdin); printf("%s\n",line);

	return 0;
}


[war@war io2]$ make
gcc -o io2 io2.o
[war@war io2]$ ./io2
line1
line1

line2
line2

line3
line3

root:x:0:0:root:/root:/bin/bash

bin:x:1:1:bin:/bin:/sbin/nologin

daemon:x:2:2:daemon:/sbin:/sbin/nologin

adm:x:3:4:adm:/var/adm:/sbin/nologin



2.open .. close .. dup ..close

(1)open(file) 打开stdin将要重定向的文件。这个调用返回一个文件描述符,这个文件描述符不是0,因为0在当前已经被打开了。

(2)close(0) 将文件描述符0关闭。文件描述符0现在已经空闲来。

(3)dup(fd) 系统将文件描述符fd做来一个复制。此次复制使用了最低可用文件描述符号,即0.这样就将磁盘文件与文件描述符0链接在了一起。

(4)close(fd) 最后使用close(fd)来关闭文件的原始连接,只留下文件描述符0的连接。


#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(){
	int fd;
	char line[128];
	
	fd = open("/etc/passwd",O_RDONLY); // 首先打开文件fd,得到3
	
	close(0);                          // 关闭文件标志符0,即stdin
	
	dup(fd);                           // 复制,fd',得到最低文件描述符0
	
	close(fd);                         // 关闭fd
	
	fgets(line,100,stdin);             // 从stdin=0获取字符串,此时0标记的是
					   // fd'
	printf(line);                      // 输出line
	
	return 0;
}



[war@war pipe4]$ make
cc    -c -o pipe4.o pipe4.c
gcc -o pipe4 pipe4.o
[war@war pipe4]$ ./pipe4
root:x:0:0:root:/root:/bin/bash




3.open..dup2..close

与2相似,只是dup2(fd,0)将close(0),dup(fd)合在一起

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>


int main(){
	int fd;
	int newfd;
	char line[100];

	fgets(line,100,stdin); printf("%s\n",line);
	fgets(line,100,stdin); printf("%s\n",line);
	fgets(line,100,stdin); printf("%s\n",line);

	fd = open("/etc/passwd",O_RDONLY);
	newfd = dup2(fd,0);
	if(newfd != 0){
		fprintf(stderr,"Cound not duplicated fd to 0\n");
		exit(1);
	}
	close(fd);
	
	fgets(line,100,stdin); printf("%s\n",line);
	fgets(line,100,stdin); printf("%s\n",line);
	fgets(line,100,stdin); printf("%s\n",line);
	
	return 0;
}


[war@war io3]$ make
cc    -c -o io3.o io3.c
gcc -o io3 io3.o
[war@war io3]$ ./io3
line1
line1

line2
line2

line3
line3

root:x:0:0:root:/root:/bin/bash

bin:x:1:1:bin:/bin:/sbin/nologin

daemon:x:2:2:daemon:/sbin:/sbin/nologin

[war@war io3]$



二.为其他程序重定向i/o

who>userlist

close(1);

creat("f");

exec();

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(){
	int pid;
	int fd;
	printf("About to run who into a file\n");
	
	if((pid = fork()) == -1){
		perror("fork");
		exit(1);
	}
	
	if(pid == 0){
		close(1);
		fd = creat("userlist",0644);	
		execlp("who","who",NULL);
		perror("execlp");
		exit(1);
	}
	
	if(pid!=0){
		wait(0);
		printf("Done runing who. result in userlist.\n");
	}
	
	return 0;
}
[war@war io4]$ make
gcc -o io4 io4.o
[war@war io4]$ ./io4
About to run who into a file
Done runing who. result in userlist.
[war@war io4]$ cat userlist
war      tty1         2015-10-04 14:08 (:0)
war      pts/0        2015-10-04 14:22 (:0.0)




重定向到文件的小结:

(1)标准输入,输出以及错误输出分别对应文件描述符0,1,2;

(2)内核总是使用最低可用文件描述符;

(3)文件描述符集合通过exec调用传递,且不会被改变

shell 使用进程通过fork()产生子进程与子进程调用exec之间的时间间隔来重定向标准输入,输出到文件


三,管道编程

管道是内核中一个单向的数据通道。管道有一个读取端和写入端。

pipe调用由使用来最低可用文件描述符。


pipe    创建管道

头文件: #include<unistd.h>

原型:result = pipe(int array[2]);

参数:array 包含两个int类型的数组

返回值:-1错误,0成功


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(){
	int apipe[2],i,len;
	char buf[BUFSIZ];
	
	if(pipe(apipe) == -1){  // 创建一个管道:apipe[0]读,apipe[1]写
				// 数据流向:读 <---  写, 即apipe[1]流向apipe[0]
		perror("pipe"); // 如果创建失败,输出错误原因,退出
		exit(0);
	}

	fgets(buf,BUFSIZ,stdin);   // 从输入端读取数据
	
	len = strlen(buf);         // 获取读入的字符串长度	

	write(apipe[1],buf,len);   // 将数据写入到apipe[1],写-->读

	for(i=0;i<len;i++)
		 buf[i]='-';       // 将buf里面的数据全部变为'-'
	
	read(apipe[0],buf,len);    // 从apipe[0]中读取数据,apipe[1]和apipe[0]
				   // 是连接在一起的apipe[1] ---> apipe[0]	
				   // 而前面已经在apipe[1]中写入了数据buf,buf的
				   // 数据来自stdin.
	write(1,buf,len);          // 1即代表stdout,向stdout写数据,则输出到屏幕
                                   // 所以整个一圈下来,输入的数据将会输出到屏幕
	return 0;
}

[war@war pipe2]$ make
gcc -o pipe2 pipe2.o
[war@war pipe2]$ ./pipe2
abc
abc




使用管道向自己发送数据:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CHILD_MESS  "I want a cookie\n"
#define PAR_MESS    "Testing...\n"
#define oops(m,x)   {perror(m);exit(x);}

int main(){
	int pipefd[2];
	int len;
	char buf[BUFSIZ];
	int read_len;
	
	if(pipe(pipefd) == -1){
		oops("can not get a pipe",1);
	}
	else{
		printf("pipefd[0]=%d,pipefd[1]=%d\n",pipefd[0],pipefd[1]);
	}
	switch(fork()){
	case -1:oops("cannot fork",2);
	case  0:
		len = strlen(CHILD_MESS);
		while(1){
			if(write(pipefd[1],CHILD_MESS,len) == -1){
				oops("Write",3);
			}
			sleep(5);
		}
	break;
	default:
		len = strlen(PAR_MESS);
		while(1){
			if(write(pipefd[1],PAR_MESS,len)!=len)
				oops("write",4);
			sleep(4);
			read_len=read(pipefd[0],buf,BUFSIZ);
			if(read_len<=0)
				break;
			write(1,buf,read_len);
		}
	break;
	}
	return 0;
}

[war@war pipedemo]$ make
cc    -c -o pipedemo.o pipedemo.c
gcc -o pipedemo pipedemo.o
[war@war pipedemo]$ ./pipedemo
pipefd[0]=3,pipefd[1]=4
Testing...
I want a cookie
Testing...
I want a cookie
Testing...
I want a cookie
Testing...
I want a cookie
Testing...
Testing...
I want a cookie
Testing...
I want a cookie
^C

在程序中。显示来从键盘到进程,从进程到管道,再从管道到进程以及从进程回到终端的数据传输流。


使用fork来共享管道

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define SON_STR  "sub process string...\n"    // 子进程字符串

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define SON_STR  "sub process string...\n"    // 子进程字符串

int main(){
	int pid;
	int apipe[2];
	char buf[128];
				// 首先创建一个管道
	if(pipe(apipe) == -1){  // 如果创建失败,则输出错误原因,并退出
		perror("pipe");
		exit(1);
	}
	
	pid = fork();          // 创建一个子进程
	
	switch(pid){
	case -1:               // 如果创建失败 
		perror("fork");// 输出错误原因
		exit(1);       // 并退出
	break;
	case 0:                // 创建成功,0说明是子进程
		write(apipe[1],SON_STR,strlen(SON_STR)); // 向子进程的写入管道
							 // 写入数据
	break;                                
	default:                          // 非0则是父进程
		read(apipe[0],buf,128);   // 从父进程的读管道接口读取数据
		write(1,buf,strlen(buf)); // 将读取的数据输出到屏幕      
	break;
	}
	return 0;
}

 

[war@war pipe3]$ ./pipe3
sub process string...




在这个程序中,shell首先创建管道,然后调用fork创建两个新进程,再将标准输入和输出重定向到创建的管道,最后通过exec来执行两个程序。当父进程调用fork的时候,管道的两端都被复制到子进程当中。只有由共同父进程的进程之间才可以用管道连接。


四,管道并非文件

    管道在很多方面都类似于普通文件。进程使用write将数据写入到管道,又通过read把数据读出来。像文件一样,管道是不带由任何结构的字节序列。另一方面,管道又与文件不同,例如文件的结尾也是否适用于管道呢?

1.从管道中读取数据

(1)管道读取阻塞

    当进程试图从管道中读取数据时,进程被挂起直到数据被写进进程。那么如何避免进程无止境地等待下去呢?

(2)管道的读取结束标志

    当所有的写者关闭来管道的写数据端时,试图从管道读取数据的调用返回0,这意味著文件的结束。

(3)多个读者可能引起麻烦

    管道是一个队列。当进程从管道中读取数据之后,数据已经不存在了。如果两个进程都试图对同一个管道进行读操作,在一个进程读取一些之后,另一个进程读到的将是后面的内容。他们读到的数据必然是不完整的,除非两个进程使用某种放法来协调他们对管道的访问。

2.向管道中西数据

(1)写入数据阻塞直到管道有空间去容纳新的数据

    管道的容纳数据的能力要比磁盘文件差得多。当进程试图对管道进行写操作时,此调用将挂起直到管道中有足够的空间去容纳新的数据。如果进程想写入1000个字节,而管道中现在只能容纳500个字节,那么这个写入调用就只好等待直到管道中再有500个字节空出来。如果某进程试图写100万字节,那结果会怎么样呢?调用会不会永远等待下去呢?

(2)写入必需保证一个最小的块大小

    POSIX标准规定内核不会拆分小于512字节的块。而Linux则保证管道中可以存在4096字节的连续内存。如果两个进程向管道写数据,并且每一个进程都限制其消息不大于512字节,那么这些消息都不会被内核拆分。

(3)若无读者存在读取数据,则写操作执行失败

    如果所有的读者都已将管道的读取端关闭,那么对管道的写入调用将会执行失败。如果在这种情况下,数据还可以被接受的话,他们会到哪里去呢?为了避免数据丢失,内核采用了两种方法来通知进程:“此时的写操作是无意义的”。首先,内核发送SIGPIPE消息给进程。若进程被终止,则无任何事情发生。否则write调用返回-1,并且将errno置为EPIPE。(这一节摘自《Unix/Linux编程实践教程》)




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值