重定向(dup、dup2、dup3)--Linux

🚩重定向是什么?

在上篇博客[文件描述符]中我曾提到了一个有意思的证明:进程在最开始运行的时候,首先打开了三个文件,分别是标准输入流、标准输出流、标准错误输出流。证明的时候我是把标准输出留给关闭了,然后紧接着创建的文件就会占用已关闭的标准输出流,使得本该流向显示器的数据流向了新创建的文件。先不谈底层的原理,就只看表象,就像是使数据流的方向从一个方向,指向了另一个方向,完成了数据流的方向重定向。现在再次理解重定向就好理解得多了:重新锚定方向

🚩dup系列函数实现重定向

就如我上面提到的证明过程,虽说最后也实现了重定向的操作,但是这都是我们手动一步一步设计的环节,先关闭再创建,并且是重定向哪个,哪个就要关闭,关闭和创建之间,不能有其他文件的创建,否则就会把关闭的文件给占用掉了,从而导致定向到了错误的地方。

晕😵~,感觉好麻烦。但是别担心,操作系统给我们提供了函数接口,帮我们在文件管理的层面直接解决这个问题。

image-20221106210452858

上图是dup系列的重定向函数,同样的,也都是系统提供给我们的函数,属于直接对内核数据进行修改。

由于看着实在是太多而且还是英文,这里就由我一步一步从dup开始解析吧。

🍁dup

image-20221106230337023

头文件:unistd.h

参数:oldfd–旧的文件描述符(意味着最终要指向的文件,用old来描述确实很奇怪,但是没办法,将就着理解叭)

返回值:在成功的情况下,返回新文件描述符。如果出现错误,则返回-1,并适当设置errno。

返回的文件描述符重新指向了oldfd指向的文件,这个新的文件描述符是没有被使用的最小的文件描述符。

⌨整点代码测试:

#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PATH_NAME "log.txt"
using namespace std;
int main()
{
    umask(0);
    int oldfd = open(PATH_NAME, O_CREAT | O_RDWR | O_TRUNC, 0600);
    if (oldfd < 0)
    {
        cerr << strerror(errno) << endl;
        exit(2);
    }
    cout << "oldfd: " << oldfd << endl;
    int newfd = dup(oldfd);
    cout << "newfd: " << newfd << endl;

    const char *str = "Hello, Kangkang, this is Michael\n";
    char output[1024];
    write(newfd, str, strlen(str));
    lseek(oldfd, 0, SEEK_SET);
    ssize_t size = read(oldfd, output, sizeof(output) - 1);
    if (size)
    {
        output[size] = '\0';
        cout << output;
    }
    else
    {
        cout << "do nothing" << endl;
    }
    close(oldfd);
    close(newfd);
    return 0;
}

💻:

image-20221106223158740

首先以读写的方式打开一个文件log.txt,返回一个文件描述符oldfd,接着调用dup返回一个新的文件描述符,这个文件描述符按道理讲也是指向log.txt的,接着我们以新的文件描述符去从log.txt中读取,事实证明确实可以,dup的功能得到证实。

🍁dup2

dup虽然可以完成重定向,但是使用起来也不是那么方便,因为它只能重定向未被使用的最小的文件描述符,对已有的文件不能完成重定向。因此为了解决这个问题,又提供了dup的进阶版–dup2,可以实现指定的文件描述符newfd的重定向。

image-20221106230452314

头文件:unistd.h

参数:

oldfd–最终指向的文件的 文件描述符

newfd–需要被重定向的文件描述符

newfd与oldfd一样的时候,什么都不做,直接返回newfd

返回值:在成功的情况下,返回新文件描述符。如果出现错误,则返回-1,并适当设置errno。

这里的newfd就是我们用户指定的需要被重定向的文件描述符。

⌨整点代码测试:

#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PATH_NAME "log.txt"
using namespace std;
int main()
{
    umask(0);
    int oldfd = open(PATH_NAME, O_CREAT | O_RDWR | O_TRUNC, 0600);
    if (oldfd < 0)
    {
        cerr << strerror(errno) << endl;
        exit(2);
    }
    cout << "oldfd: " << oldfd << endl;
    int newfd = dup2(oldfd, 1);        //使得标准输出流指向log.txt
    if (newfd == -1)
    {
        cerr << strerror(errno) << endl;
        exit(2);
    }

    //以下的cout全部都是向log.txt中输出数据
    cout << "newfd: " << newfd << endl; 

    const char *str = "Hello, Kangkang, this is Michael\n";
    cout << str;
    
    //使得标准输入流指向log.txt
    newfd = dup2(oldfd,0);
    if (newfd == -1)
    {
        cerr << strerror(errno) << endl;
        exit(2);
    }
    //重新设置文件偏移量,从文件的起始位置开始
    lseek(oldfd,0,SEEK_SET);
    char output[1024];
    cin >> output;  //仅仅从文件log.txt中读取一次数据
    cout << output << endl;  //再次将其放入文件中
    close(oldfd);
    close(newfd);
    return 0;
}

💻:

image-20221106234417970

之所以只有一个"newfd:"被读取到了,是因为cin提取流默认读到空格’ ‘或者换行符’\n’都会停止读取。并且我们发现上面的代码我们使用了lseek来改变文件的偏移量,改变了oldfd的也会导致newfd的文件偏移量发生同步变化,这也就是说新的文件描述符不仅仅只指向了oldfd,并且还共享文件偏移量和文件的状态。

讲真的,一般情况下,我们用到最多的就是指定文件的重定向dup2,毕竟重定向一般都是有需求了才会重定向的叭🤔。

🍁dup3

image-20221107105459533

dup3和dup2功能相似,只是多了一个参数:flags,并且要宏定义_GNU_SOURCE,这样就可以设置flags为库里宏定义好的O_CLOEXEC,那这样做的意义是什么呢?

🔺首先我们得清楚O_CLOEXEC的作用是干嘛的:

由于在调用exec系列函数([进程替换]((1条消息) 进程控制–Linux_皮皮蜥的博客-CSDN博客))时,由于进程里的程序被替换,文件描述符就会被释放,但是文件表项却没被释放,有点像内存泄漏,相当于栈上的指针被释放了,但是指针指向的堆上的空间并没有被释放。也就是说,假如我们知道了替换程序前的文件描述符,就可以在替换后的程序中继续对之前打开的文件进行操作,这意味着新的程序会继承被替换的程序的文件表项(文件指针数组)。

但是我们替换程序的时候一般的需求都是使用新的程序,很少需要用到之前已打开的文件,这就意味着我们需要去关闭那些我们在新的程序中用不到的文件(闲置文件)。更严重的情况是如果不关闭某些文件,由于替换之后文件描述符的丢失,会造成一些文件始终无法关闭的情况,造成不可预测的结果。但是如果已经打开了好多文件,手动去一个一个的关闭肯定很麻烦,于是我们可以在open这些文件的时候在open的flags里也加上O_CLOEXEC使得在进程替换时能够直接识别到文件描述符中有O_CLOEXEC开启的标志,在进程替换之前就把各个相应的文件全部自动关闭,省心省力。

那么绕了一圈,在dup3中设置flags的用途又是干嘛的呢?

由于给newfd加了O_CLOEXEC标志,进程替换前会将newfd会被自动关闭,也就是把重定向给关闭了!然后替换的新的程序继承的文件描述符就没有相关的重定向了。

⌨整点代码测试一下:

#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PATH_NAME "log.txt"
using namespace std;
int main()
{
    umask(0);
    int fd =open(PATH_NAME,O_CREAT | O_RDWR | O_TRUNC,0600);
    if(fd<0)
    {
        cerr << strerror(errno) << endl;
        exit(2);
    }


    const char *str = "Hello, Kangkang, this is Michael";

    //一旦发生进程替换就把对应的重定向newwfd关闭,相当于cout无法被正常使用了(1所对应的文件指针就变成nullptr)。
    dup3(fd,1,O_CLOEXEC);  
    cout<<str<<endl;
    execlp("ls","ls","-al",nullptr);
    cerr<<"something wrong"<<endl;
    close(fd);
    return 0;
}

💻:

在这里插入图片描述

观察到发生了写入错误,并提示错误的文件描述符,这不就是因为fd:1被关闭了吗?并且由于文件异常关闭,导致log.txt中并没有数据写入。这里我是为了方便观察才使用的标准输出重定向做的例子,实际使用的时候基本都不会出现这种进程异常结束的现象,只会单纯地把重定向取消罢了。

🚩总结

重定向的知识并不简单,首先得清楚文件描述符的概念,其次得知道进程替换的具体应用细节。并且还得熟练掌握dup函数族,实际应用还是比较重要的。

我在整理这篇博客的时候也是又回头看了一下才疏通了知识脉络,希望正在学习的你能够从这篇文章中有所收获吧,有问题欢迎留言或私信,我们一起学习进步🐾

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值