您的位置:首页 > 运维架构 > Linux

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

2015-10-04 12:02 453 查看
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编程实践教程》)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: