文件重定向

文件重定向

七月 18, 2025 次阅读

先来看一段代码:

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

using namespace std;

int main()
{
    close(1);
    int fd = open("test.txt", O_WRONLY | O_CREAT, 0666);
    cout << "hello, linux!" << endl;
    close(fd);
    return 0;
}

这段代码将“标准输出文件”关闭了,我们再通过 cout 打印数据的时候,会发现数据被打印到了 test.txt 文件当中:

╭─ljx@VM-16-15-debian ~/linux_review/lesson4
╰─➤  cat test.txt
hello, linux!

这是因为标准输出文件被关闭后,文件描述符 1 变成空闲的,那么 test.txt 文件就会使用 1 号文件描述符,最终就导致 cout 函数打印的数据被 test.txt 文件接收到,而
这正是文件重定向的原理

我们通过这种方式确实实现了重定向,但若每次通过这种方式来进行文件的重定向,未免太过麻烦,而且可读性极差,此时,我们就可以用到系统提供的一个重定向接口:

dup2

1. dup2 函数原型

#include <unistd.h>

int dup2(int oldfd, int newfd);

参数说明

参数 说明
oldfd 源文件描述符(必须是一个已打开的有效 fd)
newfd 目标文件描述符(由用户指定,范围通常是 0 ~ RLIMIT_NOFILE-1

返回值

  • 成功:返回 newfd(即复制后的新文件描述符)。
  • 失败:返回 -1,并设置 errno(常见错误如 EBADF 无效 fd,EINTR 被信号中断)。

2. dup2 的核心功能

  1. 复制文件描述符

    • oldfd 指向的文件表项复制到 newfd
    • 如果 newfd 已经打开,dup2先关闭 newfd,再复制。
  2. 文件描述符重定向:

    • 常用于将标准输入(0)、标准输出(1)、标准错误(2)重定向到文件或管道。

3. 关键行为

  • newfd == oldfd:直接返回 newfd(无操作)。
  • newfd 已打开:自动关闭 newfd,再复制 oldfd
  • oldfd 无效:返回 -1errno = EBADF)。

4. 典型应用场景

(1) 重定向标准输出到文件

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

int main() {
    int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open failed");
        return 1;
    }

    // 将标准输出(fd=1)重定向到文件
    if (dup2(fd, STDOUT_FILENO) == -1) {  // STDOUT_FILENO = 1
        perror("dup2 failed");
        return 1;
    }
    close(fd);  // 关闭原始 fd(因为 1 已经指向文件)

    printf("This will be written to output.txt\n");
    return 0;
}

运行后printf 的内容会写入 output.txt,而非屏幕。


(2) 实现管道(Pipe)通信

该示例设计后面内容,感兴趣可以展开看看:

实现原理

dup2 函数的实现原理很简单,我们先来看看这张图:

dup2

该函数会将旧文件描述符内部存储的 file 指针覆盖拷贝到新文件描述符处,因此,一般我们在重定向后会把旧的文件描述符给关闭

下面是一个综合使用实例:

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

using namespace std;

int main()
{
    int fd = open("test.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
    dup2(fd, 1);
    cout << "hello, linux!" << endl;
    cerr << "error information!" << endl;
    close(fd);
    return 0;
}

这是一个将标准输出重定向的例子,而标准错误我们并没有重定向,因此我们会看到标准错误打印的信息出现在显示屏上:

╭─ljx@VM-16-15-debian ~/linux_review/lesson4
╰─➤  ./file_dump.o
error information!
// test.txt
hello, linux!
hello, linux!
hello, linux!
hello, linux!
hello, linux!
hello, linux!
hello, linux!
hello, linux!

我们将标准错误也重定向:

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

using namespace std;

int main()
{
    int fd = open("test.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
    dup2(fd, 1);
    dup2(fd, 2);
    cout << "hello, linux!" << endl;
    cerr << "error information!" << endl;
    close(fd);
    return 0;
}

然后我们会发现标准错误也被重定向到文件中去了:

hello, linux!
hello, linux!
hello, linux!
hello, linux!
hello, linux!
hello, linux!
hello, linux!
hello, linux!
hello, linux!
error information!

再看shell重定向符号使用

shell 中的 “>” 和“>>”符号可以进行输出重定向,本质上其实就是使用系统底层的 dup2 函数来实现的,当我们使用 2> 时,会将标准错误重定向到指定文件当中去

我们写一个示例测试一下:

#include <iostream>
using namespace std;

int main()
{
    int cnt = 5;
    while(cnt--)
    {
        cout << "This is a current information!" << endl;
        cerr << "This is a error information!" << endl;
    }
    return 0;
}

只重定向标准输出:

╭─ljx@VM-16-15-debian ~/linux_review/lesson4
╰─➤  ./test.o 1> test.txt
This is a error information!
This is a error information!
This is a error information!
This is a error information!
This is a error information!
╭─ljx@VM-16-15-debian ~/linux_review/lesson4
╰─➤  cat test.txt
This is a current information!
This is a current information!
This is a current information!
This is a current information!
This is a current information!

我们会发现标准错误被打印到屏幕上,而标准输出被重定向到了 test.txt 当中

只重定向标准错误:

╭─ljx@VM-16-15-debian ~/linux_review/lesson4
╰─➤  ./test.o 2> test.txt
This is a current information!
This is a current information!
This is a current information!
This is a current information!
This is a current information!
╭─ljx@VM-16-15-debian ~/linux_review/lesson4
╰─➤  cat test.txt
This is a error information!
This is a error information!
This is a error information!
This is a error information!
This is a error information!

那我如果想要让标准输出和标准错误都重定向到 test.txt 中该怎么办,有些小天才可能就要动他的小脑筋了(没错正是本人),这样不就行了吗:

╭─ljx@VM-16-15-debian ~/linux_review/lesson4
╰─➤  ./test.o 1> test.txt 2> test.txt
╭─ljx@VM-16-15-debian ~/linux_review/lesson4
╰─➤  cat test.txt
This is a error information!
This is a error information!
This is a error information!
This is a error information!
This is a error information!
ormation!

你看这结果对劲吗?

当我们运行命令 ./test.o 1> test.txt 2> test.txt 时,标准输出(stdout)和标准错误(stderr)都会被重定向到同一个文件 test.txt,但这里存在一个潜在问题:竞争条件


分析过程如下:

  1. 两个文件描述符(fd 1 和 fd 2)同时指向 test.txt

    • 1> 将 stdout(fd 1)重定向到 test.txt
    • 2> 将 stderr(fd 2)也重定向到 test.txt
  2. 写入时的竞争条件

    • stdout 和 stderr 会同时尝试写入同一个文件,但由于操作系统的文件描述符是独立的,两者的写入顺序不确定,可能导致内容交错或覆盖。
    • 例如,如果程序交替输出 stdout 和 stderr,最终 test.txt 的内容可能是乱序的。
  3. 文件被多次打开

    • 1>2> 会分别以覆盖模式O_TRUNC)打开 test.txt,后打开的会清空前一个的内容(取决于 shell 的实现)。
    • 在某些 shell(如 bash)中,最终可能只有 stderr 的内容被保留(因为 2> 是最后执行的)。

如果我们想将 stdout 和 stderr 合并写入同一个文件,应该使用以下方式:

./test.o > test.txt 2>&1

这样会把标准错误重定向搭配标准输出,从而避免了乱流问题:

╭─ljx@VM-16-15-debian ~/linux_review/lesson4
╰─➤  ./test.o 1> test.txt 2>&1
╭─ljx@VM-16-15-debian ~/linux_review/lesson4
╰─➤  cat test.txt
This is a current information!
This is a error information!
This is a current information!
This is a error information!
This is a current information!
This is a error information!
This is a current information!
This is a error information!
This is a current information!
This is a error information!