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;
}
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编程实践教程》)