文章目录
- 1. 文件描述符分配规则
- 2. 重定向接口
- dup2
- 自定义shell重定向(补充)
- 3. 标准输出和标准错误
- 4. 如何理解一切接文件
本章代码gitee地址:文件重定向
文件描述符的分配规则是从0下标开始,寻址最小的没有使用的数组位置,它的下标就是新文件的文件描述符。
我们来看一个现象:
#include
#include
#include
#include
#include
#include
#define filename "log.txt"
int main()
{
close(1);
int fd = open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);
if(fd < 0)
{
perror("open");
return 1;
}
const char *str = "hello linux\n";
int cnt = 5;
while(cnt--)
{
write(1,str,strlen(str));
}
close(fd);
return 0;
}
这段代码本应该是想1
号文件描述符写入,也就是向显示屏输出信息。我们将这个1
号文件描述符指向的显示器文件关闭了,运行结果没有显示出来,这个符合预期。可是,我们发现原本应该向显示器文件写入的内容,却写进了log.txt
这个文件。
这个过程就叫做——输出重定向。
我们将
1
号文件描述符关闭,将进程与显示器对应的关联关系去掉。这个1
号位置就空出来了,然后我们重新打开一个文件,那么正好1
号文件描述符指向的内容是空的,然后就与该文件建立关联关系,所以内容写入这个文件
如果我们想将某个内容重定向,其实不需要我们自己每次都手动关闭某个文件描述符,再去覆盖,系统为我们还提供了一些接口。
man 2 dup2
这里的并不是拷贝文件描述符,因为文件描述符只是一个数组的下标,本质上拷贝的是文件描述符指向的内容
#include
#include
#include
#include
#include
#include
#define filename "log.txt"
int main()
{
//close(1);
int fd = open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);
if(fd < 0)
{
perror("open");
return 1;
}
dup2(fd,1);
close(fd);
const char *str = "hello linux\n";
int cnt = 5;
while(cnt--)
{
write(1,str,strlen(str));
}
//close(fd);
return 0;
}
我们这里就能看到,原本应该向显示器文件输出的内容,重定向到了log.txt
文件当中。
重定向的本质就是将文件描述表当中的文件地址做拷贝。
有了这些知识原理,就能写出自己的重定向代码
不了解的可以查看这篇文章——Linux实现简易shell
#define NONE -1
#define IN_RDIR 0
#define OUT_RDIR 1
#define APPEND_RDIR 2
char *rdirfilename = NULL;
int redir = NONE;
void check_redir(char *cmd)
{
char *pos = cmd;
while(*pos)
{
if(*pos == '>')
{
if(*(pos+1) == '>')
{
*pos++ = '\0';
*pos++ = '\0';
while(isspace(*pos)) pos++;
rdirfilename = pos;
redir = APPEND_RDIR;
break;
}
else
{
*pos++ = '\0';
while(isspace(*pos)) pos++;
rdirfilename = pos;
redir = OUT_RDIR;
break;
}
}
else if(*pos == '<')
{
*pos++ = '\0';
while(isspace(*pos)) pos++;
rdirfilename = pos;
redir = IN_RDIR;
break;
}
else
{
//不改变
}
pos++;
}
}
void normalExcute(char *_argv[])
{
pid_t id = fork();
if(id < 0)
{
perror("fork fail");
return;
}
else if(id == 0)
{
//判断是否重定向
int fd = 0;
if(redir == IN_RDIR) //输入重定向
{
fd = open(rdirfilename,O_RDONLY);
dup2(fd,0);
}
else if(redir == OUT_RDIR) //输出重定向
{
fd = open(rdirfilename,O_CREAT|O_WRONLY|O_TRUNC,0666);
dup2(fd,1);
}
else if(redir == APPEND_RDIR) //追加重定向
{
fd = open(rdirfilename,O_CREAT|O_WRONLY|O_APPEND,0666);
dup2(fd,1);
}
//子进程执行命令
//execvpe(_argv[0],_argv,environ); //直接程序替换
execvp(_argv[0],_argv); //直接替换程序
exit(EXIT_CODE); //替换失败的退出码
}
else
{
//父进程等待子进程退出
int status = 0;
pid_t rid = waitpid(id,&status,0); //阻塞等待
if(rid == id)
{
lastcode = WEXITSTATUS(status);
}
}
}
这里的重定向并不会影响到之后的程序替换
因为这里的进程结构体或者是文件结构体,都是属于内核的数据结构,而我们的程序替换是由程序地址空间通过页表来进行一个替换,这也将程序替换和重定向工作进行了解耦
标准输出stdou
和标准错误stderr
都是显示器文件输出内容,那他们这两个有什么区别呢
#include
#include
#include
#include
#include
#include
#define filename "log.txt"
int main()
{
fprintf(stdout,"hello stdout\n");
fprintf(stdout,"hello stdout\n");
fprintf(stdout,"hello stdout\n");
fprintf(stderr,"hello stderr\n");
fprintf(stderr,"hello stderr\n");
fprintf(stderr,"hello stderr\n");
return 0;
}
这里我们运行,确实是发现,都是往显示器文件上输出内容
但是如果我们重定向,这里发现往标准错误输入的内容并没有重定向到我们的目标文件当中,而标准输出的内容被重定向到了目标文件
通过这里我们可以验证,>
这个输出重定向的符合,默认就是将一个号文件描述符的内容重定向到目标
但是我们也可以指定将几号文件描述符指向的内容重定向到目标文件当中
这里有个
2>&1
,意思就是将一号文件描述符的内容拷贝到二号文件描述符当中,让其和1指向同一个文件
所以操作计算机的动作,都是以进程的形式进行操作的;所以访问文件的操作,也都是以进程的形式访问的。
在计算机里面,存在着各种设备,例如键盘、显示器、磁盘、网卡…,这些都被称之为外设。
操作系统和这些外设打交道的时候,都需要它们所对应的方法,例如显示要有读的方法、键盘要有读写方法…每个外设都要有其对应读写方法,这个简称为IO
。
因为在linux
下一切皆文件,所以在操作系统看来,这些外设都是文件,而访问文件的操作,都是以进程的形式访问。linux
内核里面又提供了一个方法表的结构struct operation_func
,这里面包含了一些函数指针,函数指针可以调用这些外设的读写方法。
每打开一个文件,就会为其创建一个方法集对象,而在struct file
中,又包含了一些指针,其中就有一个可以指向这个方法集。
那么在进程创建的时候,进程有对应的文件描述符表,这个表就能指向这些文件,我们用户就提供系统调用,来访问这些文件。
所以从这个struct file
往上,我们并不用关心底层访问的是什么东西,我们看到就是一切皆文件!
这里指针指向哪一个对象,就访问哪一个对象,这个不就是多态的逻辑吗?