dup、dup2实现文件描述符重定向(标准输入、标准输出、标准错误输出)

目录

dup函数

dup2函数

重定向标准输入

重定向标准输出

重定向标准错误输出

重定向恢复

总结


     在前文中,可以知道,文件描述符实际上是指向文件表项的指针数组索引,也就相当于每个文件描述符都对应一个文件表项,最终对应一个文件,而文件描述符重定向,则是让一个文件描述符指向另一个特定的文件表项,最终使得不同的文件描述符指向同一个文件表项,常用到的函数就是dup、dup2以及fcntl等函数。

        此外,对于linux下系统默认的描述符,0、1、2是最特殊的,它们分别对应了标准输入、标准输出和标准错误输出,如下所示:

名称文件描述符含义设备说明
STDIN0标准输入键盘获取执行时所要的输入数据
STDOUT1标准输出显示器输出执行后的输出结果
STDERR2标准错误输出显示器输出执行时的错误信息

        以下主要围绕这3个系统默认的文件描述符来讲一下dup函数和dup2函数。

dup函数

         dup函数的原型为int dup(int oldfd);

         该函数的作用是,返回一个新的文件描述符(可用文件描述符的最小值)newfd,并且新的文件描述符newfd指向oldfd所指向的文件表项。如以下调用形式:int newfd  =   dup(oldfd);假设调用时oldfd = 1(即系统默认的标准输文件描述符),调用后newfd = 3(假设),那么文件描述符3所对应的文件表项就是文件描述符1对应的文件表项。

        通过这样,就可以把一个普通的文件描述符(这里为3)重定向到标准输出文件描述符(文件描述符为1),文件描述符为1对应的设备是显示器,也就是说,向文件描述符1进行write,就可以打印在显示器上,此时如果将一个文件描述符3通过dup重定向到文件描述符1上,那么也就相当于文件描述符3对应的设备也是显示器,那么向文件描述符3进行write,最终结果也会打印在显示器上,如下所示:

#include <string.h>
#include <unistd.h>
#include <iostream>
#include <fcntl.h>
#include <errno.h>

int main()
{
	const char *str = "hello world!\n";
	int newfd = -1;
	newfd = dup(1);    //将newfd重定向到标准输出

	std::cout<<"newfd = "<<newfd<<std::endl;
	write(newfd,str,strlen(str));    //向newfd中写入字符串
        close(newfd);
	return 0;
}

        可以看到,虽然是对newfd进行了write,但是最终字符串是打印到了屏幕上,即newfd重定向到了标准输出上。

dup2函数

        dup2函数原型为int dup2(int oldfd,int newfd);

        dup2函数与dup函数的功能大致相同,不过仍然是有差别的。

        dup函数是返回一个最小可用文件描述符newfd,并让其与传入的文件描述符oldfd指向同一文件表项

        而dup2则是直接让传入的参数newfd与参数oldfd指向同一文件表项,如果newfd已经被open过,那么就会先将newfd关闭,然后让newfd指向oldfd所指向的文件表项,如果newfd本身就等于oldfd,那么就直接返回newfd。因此,传入的newfd既可以是open过的,也可以是一个任意非负整数,总之,dup2函数的作用就是让newfd重定向到oldfd所指的文件表项上,如果出错就返回-1,否则返回的就是newfd。如下所示:

#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
	const char *str = "hello world!\n";

	dup2(1,5);    //将“5”重定向到标准输出
	write(5,str,strlen(str));   //向文件描述符5写入数据

	close(5);
	return 0;
}

       可以看到,虽然这里的“5”并没有对应一个打开的文件,但是也可以只用dup2函数,让“5”作为一个文件描述符重定向到标准输出,最终向文件描述符“5”进行write,就相当于是向标准输入的设备显示器进行输出,也就在屏幕中打印出来字符串。

       通过上面的阐述,接下来看看如何重定向标准输入、标准输出以及标准错误输出。

重定向标准输入

        前面提到,标准输入所对应的设备是键盘,也就是说,标准输入相应的文件描述符,当它进行read时,实际上是read键盘输入的数据,而如果不想让键盘作为标准输入呢?比如说让程序从某个文件中读取输入数据,这就需要重定向标准输入了。

        举个例子,我用一个文件描述符3对应一个打开的文件A,然后调用dup2(3,0)函数,这样就使得标准输入文件描述符0重定向到了文件描述符3所对应的文件表项上,原本应该从键盘获取数据,现在变成从文件A获取数据,此时如果调用read或者其他标准输入函数如cin、getline、getchar等函数,都是从文件A中读取数据。如下所示:

        先向外部文件test.txt中写入以下测试内容:

#include <string.h>
#include <unistd.h>
#include <iostream>
#include <fcntl.h>

using namespace std;

int main()
{
	string rdstr;
	int fd = -1;
	if((fd = open("test.txt",O_RDWR)) == -1)
	{
		cout<<"open failed !"<<endl;
		return -1;
	}
	dup2(fd,0);    //重定向标准输入到外部文件test.txt中

	while(getline(cin,rdstr))     //用getline从标准输入中获取数据
	{
		cout<<rdstr<<endl;    //通过标准输出将读入的数据打印出来
	}

        close(fd);
	return 0;
}

        可以看到,这里虽然调用了getline函数,它会从标准输入中获取数据,在一般情况下会阻塞等待键盘输入,但是由于这里标准输入重定向到了外部文件test.txt中,因此getline就直接从test.txt中获取每一行数据,与键盘输入无关。

重定向标准输出

        标准输出的设备是显示器,通过标准输出文件描述符1进行write时,数据会直接输出到显示器上。那么如果不想让标准输出输出到显示器上呢?比如说想让cout、printf直接将数据输出到文件中,那么就需要重定向标准输出了。如下所示:

#include <string.h>
#include <unistd.h>
#include <iostream>
#include <fcntl.h>
#include <errno.h>

using namespace std;

int main()
{
	int fd = -1;
	if((fd = open("test.txt",O_RDWR|O_CREAT|O_TRUNC)) == -1)  //先将test.txt的文本内容清空
	{
		cout<<"open failed !"<<endl;
		return -1;
	}
	dup2(fd,1);   //重定向标准输出到外部文件test.txt

	cout<<"重定向标准输出测试!"<<endl;    //向标准输出输出数据
        close(fd);
	return 0;
}

        将标准输出重定向到外部文件test.txt后,原本标准输出会将数据输出到显示器上,重定向后就将数据输出到了外部文件中。虽然使用cout进行了标准输出,但是程序执行后,显示器上并无任何输出,通过more查看外部文件内容,可以看到cout的数据最终输出到了外部文件中。

重定向标准错误输出

        标准错误输出实际上与标准输出类似,都是将数据输出到显示器上,只不过标准错误输出是输出错误信息,C语言中常用的错误输出就是perror了,如下面打开一个不存在的文件,就会直接在显示器上输出报错信息:

#include <fcntl.h>
#include <stdio.h>

using namespace std;

int main()
{
	open("123.txt",O_RDWR);

	perror(NULL);

	return 0;
}

        现在想把错误信息直接输出到外部文件中,就可以将标准错误输出进行重定向,如下所示:

#include <unistd.h>
#include <iostream>
#include <fcntl.h>
#include <stdio.h>
using namespace std;

int main()
{
	int fd = -1;
	if((fd = open("test.txt",O_RDWR|O_CREAT|O_TRUNC)) == -1)  //打开并清空文件
	{
		cout<<"open failed !"<<endl;
		return -1;
	}
	dup2(fd,2);   //重定向标准错误输出到外部文件中

	open("123.txt",O_RDWR);   //打开一个不存在的文件
	perror("重定向标准错误输出测试");    //输出错误信息

        close(fd);
	return 0;
}

        可见,重定向标准错误输出之后,执行程序并不会输出任何信息,但是查看test.txt文件发现,错误信息写入到了该文件中。

        通过3个重定向的例子也可以发现dup2相较于dup函数的好处:可以让一个已打开的文件描述符(如这里的0、1 、2)重定向到另一个已打开的文件描述符上(如外部文件)。

重定向恢复

       在进行重定向后,如果想要恢复到重定向之前的状态,可以在重定向之前用dup函数保留该文件描述符对应的文件表项,然后在需要恢复重定向的时候使用dup2重定向到原来的文件表项,以重定向后恢复标准输出为例,如下所示:

#include <unistd.h>
#include <iostream>
#include <fcntl.h>
#include <stdio.h>
using namespace std;

int main()
{
	int fd = -1;
	if((fd = open("test.txt",O_RDWR|O_CREAT|O_TRUNC)) == -1) //打开并清空外部文件
	{
		cout<<"open failed !"<<endl;
		return -1;
	}

	int oldfd = dup(1);     //保存标准输出对应的文件表项

	dup2(fd,1);    //重定向标准输出到外部文件test.txt中

	cout<<"重定向标准输出测试!"<<endl;    //重定向测试
  
	dup2(oldfd,1);   //将重定向后的文件描述符1再次重定向到一开始保存的标准输出对应的文件表项中

	cout<<"重定向标准输出恢复测试!"<<endl;   //重定向恢复测试

	close(fd);
	close(oldfd);
	return 0;
}

       根据运行结果可知,在第一次重定向后,cout输出信息是输出到了外部文件中,当再次重定向进行恢复之后,此时的cout就将数据输出到显示器上了,回到了最原始的标准输出。

总结

        以上介绍了dup和dup2函数,并且使用dup2函数实现了标准输入、标准输出和标准错误输出的重定向,以及dup和dup2函数共同使用实现重定向恢复。需要注意的是,在调用dup或者dup2函数之后,至少会有两个文件描述符指向同一个文件表项,由于文件表项中含有文件标志(即open时的flag)以及文件偏移等信息,因此这些信息对于这些文件描述符来说都是共享的。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值