文件重定向
先来看一段代码:
#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 的核心功能
复制文件描述符:
- 将
oldfd指向的文件表项复制到newfd。 - 如果
newfd已经打开,dup2会 先关闭newfd,再复制。
- 将
文件描述符重定向:
- 常用于将标准输入(
0)、标准输出(1)、标准错误(2)重定向到文件或管道。
- 常用于将标准输入(
3. 关键行为
newfd == oldfd:直接返回newfd(无操作)。newfd已打开:自动关闭newfd,再复制oldfd。oldfd无效:返回-1(errno = 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)通信
该示例设计后面内容,感兴趣可以展开看看:
#include <unistd.h>
#include <stdio.h>
int main() {
int pipefd[2];
pipe(pipefd); // 创建管道
if (fork() == 0) { // 子进程
close(pipefd[0]); // 关闭读端
dup2(pipefd[1], STDOUT_FILENO); // 将标准输出重定向到管道写端
close(pipefd[1]);
execlp("ls", "ls", "-l", NULL); // 执行 ls -l
} else { // 父进程
close(pipefd[1]); // 关闭写端
dup2(pipefd[0], STDIN_FILENO); // 将标准输入重定向到管道读端
close(pipefd[0]);
execlp("grep", "grep", "test", NULL); // 执行 grep test
}
}
功能:将 ls -l 的输出通过管道传递给 grep test。
实现原理
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,但这里存在一个潜在问题:竞争条件。
分析过程如下:
两个文件描述符(fd 1 和 fd 2)同时指向
test.txt1>将 stdout(fd 1)重定向到test.txt2>将 stderr(fd 2)也重定向到test.txt
写入时的竞争条件
- stdout 和 stderr 会同时尝试写入同一个文件,但由于操作系统的文件描述符是独立的,两者的写入顺序不确定,可能导致内容交错或覆盖。
- 例如,如果程序交替输出 stdout 和 stderr,最终
test.txt的内容可能是乱序的。
文件被多次打开
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!