【Linux基础IO】文件描述符分配规则 重定向
目录
前言
1. 文件fd分配规则
2. 重定向
2.1 输入重定向&输出重定向
2.2 Linux指令中的重定向
2.3 重定向中的标准输出与标准错误
总结
前言
接上文,了解完了文件,本文就来聊一聊文件描述符的分配规则,以及使用方法(重定向);
1. 文件fd分配规则
文件标识符为0、1、2的文件默认打开,read读取 stdin 文件的数据(读到buffer里),write 向 stdout中写数据;
- 标准输入:stdin(键盘) 0
- 标准输出:stdout(显示器) 1
- 标准错误:stderr(显示器) 2
比如下面的代码:
char buffer[1024];
ssize_t s = read(0,buffer,1024);//打开了标准输入
if(s > 0)
{
buffer[s] = 0;
write(1,buffer,strlen(buffer))
// printf("echo# %s\n",buffer);
}
使用示例中,没有打开0、1标识符的文件,却可以直接使用 由此也证明:0、1、2被默认打开;
如果关闭0或者2号文件,那么打开指定文件时,文件的fd就是0或者2; 验证代码如下:
close(2)
int fd = open(FILE_NAME,O_CREAT | O_WRONLY | O_TRUNC,0666);
if(fd < 0)
{
perror("open");
return 1;
}
printf("fd:%d\n" fd);
close(fd);
由此我们可以推断出fd的分配规则:
- 进程启动时默认打开0、1、2,可以直接使用0、1、2进行数据访问
- 从上到下进行扫描,没有被使用的数据位置,分配给指定的打开文件
2. 重定向
重定向的本质,其实就是修改特定文件fd的下标内容;也就是说上层的fd不变,底层fd指向内容在改变;
2.1 输入重定向&输出重定向
close(1);
int fd = open(FILE_NAME,O_CREAT | O_WRONLY | O_TRUNC,0666).
if(fd < 0)
{
perror("open");
return 1;
}
printf("fd:%d\n" fd);
printf("stdout->fd:%d\n",stdout->_fileno);
fflush(stdout);
close(fd);
上述的例子中,原本要在显示器上输出的内容,输出到了log.txt文件中;
fflush的作用:刷新缓冲区;之前提到在C语言库里边会提供一个缓冲区,如果没有fflush刷新缓冲区,数据就没办法从缓冲区写到文件当中,后续会进行详细解释;
程序逻辑图:
示例中,修改了 1 位置的文件,把原本的stdout换成了log.txt;而 printf 中的 stdout (结构体)中有一个fileno=1 ; 操作系统只认文件描述符(fileno),fileno位置的文件是谁就打开谁; 所以原本在显示器上输出的内容就修改成了打开 log.txt 进行写入 这个过程也就是重定向;
是否存在更直接的方法来达到重定向或者说,有没有一个接口进行文件描述符级别的拷贝?
如下图:
有的他就是dup接口,dup2可以做到数组内容的拷贝;
dup接口是拷贝原有的文件描述对象地址到新的位置并返回下标(文件描述符),内部可以理解为采用了引用计数;
dup2将指定文件描述符对应的内容拷贝到指定位置;
注意:文件描述符表中放的文件描述对象的地址;
拷贝过去后,两个位置的文件描述对象地址都是 log.txt,调用时如果其中个对文件进行了关闭,这种情况怎么解决?
Linux中的解决方法是引用计数,统计有几个指针指向了文件描述对象 : atomic_long_t、f_count;
将屏幕中输出的内容输出到文件中示例代码:
int main()
{
int fd = open(FILE_NAME,O_WRONLY | O_CREAT | O_TRUNC,0666);
if(fd < 0)
{
perror("open");
return 1;
}
dup2(fd,1);
//close(fd) // 关闭不需要的文件描述符
printf("hello printf!\n");
fprintf(stdout,"hello fprintf!\n")
return 0;
}
这里需要注意的点:文件描述符的重定向仅对当前进程有效,下次启动操作系统会自动将标准输出(即文件描述符 1
)恢复为默认的终端输出;
提示:fprintf
接口的作用是将格式化数据输出到指定的 文件流;
建议:
在这个代码中,将文件描述符 fd
重定向到标准输出 1
后,1
和 fd
都指向了同一个文件,所以选择关闭其中任何一个文件描述符都可以,因为它们共享同一个文件对象;在这种情况下,建议关闭 fd
,因为 1
是标准输出的文件描述符,一般情况下不推荐关闭它,特别是在程序后续可能需要标准输出的情况下;
输入重定向也是类似,可以类比一下:
int fd = open(FILE_NAME,O_RDONLY);
if(fd < 0)
{
perror("open");
return 1;
}
dup2(fd,0);//输入重定向
char buffer[1024];
fread(buffer,1,1024,stdin);
printf("%s\n",buffer);
close(fd);
2.2 Linux指令中的重定向
Linux中的重定向怎么用的?
重定向功能其实就是我们以前用的<(输入重定向)、>(输出重定向)、>>(追加重定向);
它们和我们现在了解的不一样啊,它是怎么实现的?
把ls-al输出的内容重定向到log.txt中,这些其实都是bash帮我做的,把“s-al>log.txt”这个字符串进行分割判断,后半部分log.txt 是文件名,中间的符号>是重定向的操作方式,ls -a是重定向的内容;有了这些我们就可以通过程序来实现重定向;
2.3 重定向中的标准输出与标准错误
看下面这个代码:
int main()
{
fprintf(stdout, "hello stdout\n");
fprintf(stderr, "hello stderr\n");
return 0;
}
执行结果:
myfile执行结果一条数据输出到了 log.txt 中,一条数据并没有输出到log.txt中,而是输出到了显示器中;这是为什么?
>为输出重定向,bash会修改1号位置的文件描述对象地址,而2号位置的stderr也是显示器,并不受影响,输出重定向并不影响标准错误
如果想要同时将stdout和stderr都重定向输出到log.txt文件中怎么操作?
解释:
- 前半部分:将myfile执行结果输出重定向到log.txt,
- 后半部分:标准错误的输出重定向,怎么重定向? 将1(标准输出)位置文件描述对象地址,放到2号位置一份;
通过上述的示例我们可以发现 stdout 和 stderr 它们完全不一样,虽然都可以显示到显示器上;
C语言中的 printf 和 perror;C++中的 cout 和 cerr 一个是写入到标准输出,一个是写到标准错误;
使用技巧:
我们可以通过指令间两个输出进行分离,分别写入到不同的文件中:
以上便是本文的全部内容,希望对你有所帮助,最后感谢阅读!