HOWTO · C
C 语言中的 dup2 函数
本文演示了如何在 C 语言中使用 dup2 函数。
本页内容
在 POSIX C 中,dup2(oldfd, newfd) 会让 newfd 指向与 oldfd 相同的打开文件描述。它常用于读写文件前重定向标准输入或标准输出。请检查返回值:调用失败时会返回 -1 并设置 errno。
dup2 的作用及适用场景
函数声明为 int dup2(int oldfd, int newfd);,可通过 <unistd.h> 使用。调用成功时返回 newfd。如果 newfd 已经打开,dup2 会关闭它并重新使用该描述符。关闭并复制的替换操作是原子的,因此在可能与其他线程并行运行的代码中,dup2 适合用于重定向。
这两个描述符指向同一个打开文件描述,因此会共享文件偏移量和文件状态标志:通过任一描述符读取或写入,都会推进同一个偏移量。描述符标志则属于各自的描述符;dup2 创建的新描述符不会继承 oldfd 的 FD_CLOEXEC。
如果 oldfd 和 newfd 相同,dup2 会直接返回该描述符,不会关闭它。此时该描述符必须已经有效。文件描述符编号由进程各自管理,取决于当前打开的描述符;不要假设打开文件总会得到描述符 3,也不要认为每个进程都恰好打开了三个描述符。
使用 dup2 重定向标准输出
先打开一个用于写入的目标,再将它复制到 STDOUT_FILENO。下面的简短程序会把输出重定向到临时文件,恢复原来的标准输出,然后打印捕获到的行,以便查看结果。示例使用 POSIX 临时文件和文件描述符函数。
运行此示例:
本节中的代码以及显示的标签或输出仍为英文。
#define _POSIX_C_SOURCE 200809L
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
char path[] = "/tmp/dup2-demo-XXXXXX";
int file = mkstemp(path);
if (file == -1) return 1;
int saved_stdout = dup(STDOUT_FILENO);
if (saved_stdout == -1) {
close(file);
unlink(path);
return 1;
}
if (dup2(file, STDOUT_FILENO) == -1) {
close(saved_stdout);
close(file);
unlink(path);
return 1;
}
int write_ok = puts("Hello through dup2") != EOF && fflush(stdout) == 0;
int restore_ok = dup2(saved_stdout, STDOUT_FILENO) != -1;
close(saved_stdout);
if (!write_ok || !restore_ok) {
close(file);
unlink(path);
return 1;
}
if (lseek(file, 0, SEEK_SET) == (off_t)-1) {
close(file);
unlink(path);
return 1;
}
char buffer[64];
ssize_t length = read(file, buffer, sizeof(buffer) - 1);
close(file);
unlink(path);
if (length < 0) return 1;
buffer[length] = '\0';
if (printf("Captured: %s", buffer) < 0) return 1;
return 0;
}
Captured: Hello through dup2
提供的运行结果捕获到了 Captured: Hello through dup2。程序先把第一行写入临时文件,再恢复标准输出、读取保存的字节,并打印捕获到的那一行。在生产程序中,应根据数据选择合适的目标路径和权限;此处的 mkstemp 示例会创建一个私有临时文件。
使用 dup2 重定向标准输入
若要让程序通过标准输入读取文件,请以只读方式打开文件,再将其描述符复制到 STDIN_FILENO。调用成功后,fgets 等从 stdin 读取的函数就会从该文件读取内容。复制完成后可以关闭 open 返回的描述符,因为标准输入此时也指向同一个打开文件描述。
这个完整示例会创建一个临时输入文件,将标准输入重定向到该文件,再用 fgets 读取一行,并打印该行以显示读取到的内容。临时文件就是示例的输入;将此模式用于已有文件时,可以替换临时文件的内容或调整 fgets 逻辑。
本节中的代码以及显示的标签或输出仍为英文。
#define _POSIX_C_SOURCE 200809L
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
char path[] = "/tmp/dup2-input-XXXXXX";
int input = mkstemp(path);
if (input == -1) return 1;
const char line[] = "Read through redirected stdin\n";
ssize_t written = write(input, line, sizeof line - 1);
if (written != (ssize_t)(sizeof line - 1) || lseek(input, 0, SEEK_SET) == (off_t)-1) {
close(input);
unlink(path);
return 1;
}
if (dup2(input, STDIN_FILENO) == -1) {
close(input);
unlink(path);
return 1;
}
close(input);
unlink(path);
char buffer[80];
if (fgets(buffer, sizeof buffer, stdin) == NULL) return 1;
if (printf("Read: %s", buffer) < 0) return 1;
return 0;
}
对于实际输入路径,基于 open 的写法是 int input = open("input.txt", O_RDONLY);;请检查返回值是否为 -1,调用 dup2(input, STDIN_FILENO),并在成功后关闭 input。之后从 stdin 读取的内容会来自该文件。完整示例也使用相同的步骤读取写入临时文件的那一行。如果打开、复制或读取失败,请处理错误,并关闭代码仍持有的所有描述符。
dup 与 dup2 的区别
这两个函数都会为同一个打开文件描述创建另一个描述符。dup(oldfd) 会选择并返回当前可用的最小编号描述符。dup2(oldfd, newfd) 则以你指定的编号为目标,并会先关闭已打开的目标描述符。任何可用编号都可以时使用 dup;必须让某个指定描述符(通常是标准输入或标准输出)指向源描述符时,使用 dup2。
例如,int copy = dup(file); 会返回新的描述符,失败时返回 -1。使用 copy 前要先检查返回值;两个描述符都不再需要时,应将它们关闭。返回的编号不一定是 3,而是取决于该进程当前打开的描述符。
处理错误与描述符生命周期
使用 open、dup 和 dup2 返回的描述符前,都要检查调用结果。调用失败时会返回 -1 并设置 errno;perror("dup2") 可以报告当前错误。不要把失败返回值当作描述符使用。如果多步重定向中的某一步失败,应关闭代码已打开或保存的描述符;如果标准输入或标准输出已经更改,而程序还必须使用原来的流继续运行,则应将其恢复。
重定向成功后,关闭原始源描述符不会撤销重定向:标准描述符仍保持打开,并指向共享的打开文件描述。保存的描述符及其他副本在不再需要时应关闭。更改或恢复 STDOUT_FILENO 前,请先刷新缓冲输出;C 标准 I/O 缓冲区与内核的描述符操作相互独立。做好这些检查和清理,能让描述符替换过程更可预测,也能避免对描述符编号作出错误假设。