0%

复制文件描述符的`dup2` 函数

复制文件描述符的dup2 函数

dup2 是 Unix/Linux 系统中用于复制文件描述符的函数,它允许将一个文件描述符复制到另一个文件描述符上,实现输入输出流的重定向操作,比如将stdout重定向到一个文件,亦或者是把一个文件作为输入来对待。

函数原型与头文件

1
2
3
#include <unistd.h>

int dup2(int oldfd, int newfd);

参数说明

  • **oldfd**:需要复制的源文件描述符(比如已经打开的文件、管道、套接字等)。
  • **newfd**:目标文件描述符,用于接收oldfd的副本或者重定向的描述符。若newfd已打开,系统会先关闭它,再进行复制。

返回值

  • 成功时:返回新的文件描述符(即newfd,若newfd之前已关闭则可能返回其他值)。
  • 失败时:返回-1,并设置errno错误码(如EBADF表示oldfd无效,EMFILE表示文件描述符耗尽)。

核心功能与原理

dup2 的核心作用是让newfd成为oldfd的副本,即让两个文件描述符指向同一个文件表项,从而共享文件偏移量和状态标志。例如:

  • newfd重定向为oldfd的副本后,对newfd的读写操作等同于对oldfd的操作。
  • 常见场景是重定向标准输入(STDIN_FILENO=0)、标准输出(STDOUT_FILENO=1)、标准错误(STDERR_FILENO=2)。

示例1:重定向标准输出到文件

下面的这个程序会将原本输出到终端的重定向输出到一个文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* @file linux-dup2-to-file.c
* @brief redirect stdout to file
*/

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

int main() {
int fd, old_stdout;
char *filename = "dup2-to-file-output.txt";

// keep the original stdout for restore
old_stdout = dup(STDOUT_FILENO);
if (old_stdout == -1) {
perror("dup failed");
exit(EXIT_FAILURE);
}

// open the file for writing
fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("open failed");
exit(EXIT_FAILURE);
}

// redirect stdout to file
if (dup2(fd, STDOUT_FILENO) == -1) {
perror("dup2 failed");
exit(EXIT_FAILURE);
}
close(fd); // close fd, because STDOUT_FILENO is now pointing to it

// now printf's output will be written to file
printf("This text will be written to %s\n", filename);
printf("Another line for testing...\n");

// Remember to flush the buffer otherwise the output will be buffered
fflush(stdout);

// restore original stdout
if (dup2(old_stdout, STDOUT_FILENO) == -1) {
perror("dup2 restore failed");
exit(EXIT_FAILURE);
}
// close the backup descriptor
close(old_stdout);

// after restore, output to terminal
printf("Now back to terminal output\n");

return 0;
}

编译使用的方法如下所示:

1
2
3
4
5
6
7
8
$ gcc -o linux-dup2-to-file linux-dup2-to-file.c

$ ./linux-dup2-to-file
Now back to terminal output

$ cat dup2-to-file-output.txt
This text will be written to dup2-to-file-output.txt
Another line for testing...

需要注意的是,在执行完操作后,需要调用fflush(stdout)强制刷新,不然可能不是你希望的现象。

示例2:重定向标准输入从文件

下面的这个程序会将文件的内容作为终端输入的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* @file linux-dup2-from-file.c
* @brief redirect stdin from file
*/

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

int main() {
int fd, old_stdin;
char *filename = "dup2-from-file-input.txt";
char buffer[1024];

// keep the original stdin for restore
old_stdin = dup(STDIN_FILENO);
if (old_stdin == -1) {
perror("dup failed");
exit(EXIT_FAILURE);
}

// open the file for reading
fd = open(filename, O_RDONLY);
if (fd == -1) {
perror("open failed");
exit(EXIT_FAILURE);
}

// redirect stdin to file
if (dup2(fd, STDIN_FILENO) == -1) {
perror("dup2 failed");
exit(EXIT_FAILURE);
}
close(fd); // close fd, because STDIN_FILENO is now pointing to it

// now scanf will read data from file
printf("Reading from %s:\n", filename);
while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
printf("[Read] : %s", buffer);
}

fflush(stdin);

// restore original stdin
if (dup2(old_stdin, STDIN_FILENO) == -1) {
perror("dup2 restore failed");
exit(EXIT_FAILURE);
}
close(old_stdin);

// after restore, input from terminal
printf("\n\n[Terminal] Enter something: ");
strcpy(buffer, "hello world");
printf("\n[Terminal] You entered: %s\n", buffer);
fflush(stdout);

return 0;
}

编译使用的方法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ cat dup2-from-file-input.txt 
Roses are red,
Violets are blue,
Testing dup2,
And stdin too.%

$ gcc -o linux-dup2-from-file linux-dup2-from-file.c

$ ./linux-dup2-from-file
Reading from dup2-from-file-input.txt:
[Read] : Roses are red,
[Read] : Violets are blue,
[Read] : Testing dup2,
[Read] : And stdin too.

[Terminal] Enter something:
[Terminal] You entered: hello world

dup函数的区别

dup 函数(int dup(int oldfd))也是用于复制文件描述符,但两者还是有一些区别的。比如dup 返回最小的未使用文件描述符,而dup2可以指定目标描述符newfd

  • dup2(newfd, oldfd)
    
    1
    2
    3
    4
    5
    6



    ```c
    close(newfd);
    return dup(oldfd);

应用场景

这个函数主要用在比如,IO重定向(类似于shell的> file)、管道通信(父子进程的通信)、日志系统(同时写到终端和日志文件)或者网络编程中(将网络套接字重定向到输入和输出)。

注意事项

  1. 文件描述符关闭顺序:重定向时若newfd已打开,dup2会先关闭它,可能导致数据丢失。
  2. 原子性dup2是原子操作,无需担心多线程环境下的竞争条件。
  3. 资源管理:复制的描述符共享文件表项,需确保正确关闭(避免文件描述符泄漏)。
  4. 错误处理:始终检查dup2的返回值,处理可能的错误(如文件不存在、权限不足)。

通过合理使用dup2,可以灵活控制程序的输入输出流向,实现复杂的系统编程需求。

在实际开发中,它常与管道、套接字等结合使用,是构建进程间通信和重定向机制的核心工具。

处无为之事,行不言之教;作而弗始,生而弗有,为而弗恃,功成不居!

欢迎关注我的其它发布渠道

Powered By Valine
v1.5.2