1、标准IO与重定向
所有的Unix IO重定向都基于标准数据流的原理。三种数据流分别是标准输入、标准输出、标准错误输出。所有的Unix工具都使用三种流模型,这三种流的每一种都是一个特别的文件描述符0、1、2。标准输入stdin的文件描述符是0,标准输出stdout的文件描述符是1,标准错误输出stderr的文件描述符是2。Unix假设文件描述符0、1、2已经被打开,可以分别进行读、写和写的操作。
通常通过shell命令行运行Unix系统工具时,stdin、stdout、stderr连接在终端上。使用重定向标志,命令cmd>filename告诉shell将文件描述符1定位到文件。
关于shell重定向的一些概念:
- shell并不把重定向标记和文件名传给程序
- 重定向可以出现在命令行的任何地方
- 许多版本的shell都提供对重定向其他文件描述符的支持
- Unix进程使用文件描述符0、1、2作为标准输入、输出和错误的通道
- 当进程请求一个新的文件描述符的时候,系统内核将最低可用的文件描述符赋给它
2、将当前进程的stdin重定向到文件
将stdin重定向以至可以从文件中读取数据,进程并不是从文件读取数据,而是从文件描述符读数据,如果将文件描述符0定位到一个文件,那么此文件成为标准输入的源。通常标准输入的源连接在终端上。以下三种将标准输入定位到文件的方法。
(1)close then open
这种方法先将0文件描述符关闭,将标准输入与终端设备的连接切断,然后使用open(filename,O_RDONLY)打开一个想连接到stdin上的文件,因为此时0是最低可用文件描述符,所以打开的文件被连接到标准输入上去了。
(2)open close dup close
这种方法先打开要重定向的文件,然后返回一个文件描述符,这个文件描述符肯定不是0,因为0已经打开了;然后将文件描述符0关闭;再然后系统调用dup(fd)将文件描述符fd做一个复制,此次复制使用的最低文件描述符号也就是0,这样磁盘文件与文件描述符0连接在一起了;最后关闭原始的fd,这样只留下文件描述符0与磁盘文件连接。
dup、dup2系统调用:
- dup():复制一个文件描述符,newfd=dup(oldfd),dup复制了文件描述符oldfd使得oldfd和newfd两个文件描述符连接到同一个打开的文件,只是文件描述符号不同而已。
- dup2():复制一个文件描述符,newfd=dup2(oldfd,newfd),dup2的过程是先关闭了文件描述符newfd的连接,然后将文件描述符oldfd复制给了文件描述符newfd,使得oldfd和newfd两个文件描述符连接到同一个打开的文件。
(3)open dup2 close
这种方法是通过系统调用dup2实现的。
3、将其他程序的IO重定向到文件:who>filename
- 首先进程运行在用户空间中,文件描述符1连接到终端设备上。
- 然后父进程调用fork,子进程出现了,此进程和原始进程运行相同的代码、数据、文件描述符,因此子进程的文件描述符1指向终端,然后子进程调用close(1),关闭文件描述符1。
- 子进程关闭文件描述符1之后,文件描述符1变成了最低未用文件描述符,子进程试着打开文件filename。
- 子进程调用creat(filename,mode),文件描述符1连接到filename,子进程的标准输出被重定向到filename,子进程然后调用exec运行who。
- 子进程执行who,子进程中的代码和数据都被who的代码和数据替代,然而文件描述符被保留下来。打开的文件并非是代码也不是数据,它们属于进程的属性,因此调用exec并不改变它们。
- 最终who命令将当前用户列表送至文件描述符1。
4、重定向到文件总结
- 标准输入、输出及错误输出分别对应于文件描述符0、1、2
- 内核总是使用最低可用文件描述符
- 文件描述符集合通过exec调用传递且不会被改变
5、具体实现程序
stdinredir1.c
使用close then open将当前进程的stdin重定向到文件
#include<stdio.h>
#include<fcntl.h>
int main()
{
int fd;
char line[100];
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
close(0);
fd=open("/etc/passwd",O_RDONLY);
if(fd!=0)
{
fprintf(stderr,"Could not open data as fd 0\n");
exit(1);
}
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
return 0;
}
stdinredir2.c
使用open close dup close将当前进程的stdin重定向到文件
#include<stdio.h>
#include<fcntl.h>
int main()
{
int fd;
int newfd;
char line[100];
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
fd=open("/etc/passwd",O_RDONLY);
close(0);
newfd=dup(fd);
if(newfd!=0)
{
fprintf(stderr,"Could not open data as fd 0\n");
exit(1);
}
close(fd);
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
return 0;
}
whotofile.c
将其他程序的IO重定向到文件:who>filename
#include<stdio.h>
#include<fcntl.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(NULL);
printf("Done running who.result in userlist\n");
}
return 0;
}