我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 dup 和 dup2 函数,它们用于复制一个已存在的文件描述符 (file descriptor)。
1. 函数介绍 dup 和 dup2 是 Linux 系统调用,它们的功能是创建一个指向同一文件表项 (open file description) 的新文件描述符。
简单来说,当你调用 dup 或 dup2 时,你得到的是一个别名或副本,这个新文件描述符和原始文件描述符指向同一个打开的文件,共享文件的:
文件偏移量 (file offset): 通过一个描述符读写会改变文件位置,通过另一个描述符读写会从新的位置开始。
状态标志 (status flags): 如 O_APPEND, O_NONBLOCK 等。
文件锁 (file locks): 通过任何一个描述符获取的锁,对另一个描述符也有效。
它们最常见的用途是重定向标准输入、标准输出或标准错误。例如,将一个程序的输出重定向到文件,而不是终端。
你可以把文件描述符想象成一个指向文件的“把手”。dup 就像是给这个“把手”又做了一个一模一样的复制品。你拿着任何一个“把手”都能操作同一个文件,而且它们的状态是同步的。
2. 函数原型 1 2 3 4 5 6 7 8 #include <unistd.h> int dup (int oldfd) ;int dup2 (int oldfd, int newfd) ;
3. 功能 dup(int oldfd):
dup2(int oldfd, int newfd):
复制文件描述符 oldfd,并强制使复制得到的新文件描述符的号码为 newfd。
如果 newfd 已经打开(指向另一个文件),dup2 会在复制前先关闭 newfd(相当于先调用 close(newfd))。
如果 oldfd 和 newfd 相同,dup2 什么都不做,直接返回 newfd。
4. 参数 dup:
int oldfd: 要被复制的现有有效文件描述符。
dup2:
int oldfd: 要被复制的现有有效文件描述符。
int newfd: 请求的新文件描述符号码。
5. 返回值 成功时:
失败时:
返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EBADF oldfd 或 newfd 无效,EMFILE 进程打开的文件描述符已达上限等)。
6. 相似函数,或关联函数
fcntl: dup(oldfd) 等价于 fcntl(oldfd, F_DUPFD, 0)。fcntl 提供了更灵活的复制方式,例如 F_DUPFD_CLOEXEC 可以在复制时设置 FD_CLOEXEC 标志。
close: dup2 在复制前如果 newfd 已打开,会隐式调用 close(newfd)。
open: 用于获取最初的文件描述符。
read, write: 对复制后的文件描述符进行操作。
7. 示例代码 示例 1:使用 dup 和 dup2 重定向标准输出 这个例子演示了如何使用 dup 和 dup2 来保存原始标准输出,然后将标准输出重定向到一个文件,最后再恢复标准输出。
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 61 62 63 64 65 66 67 68 69 70 71 72 int main() { int saved_stdout; // 用于保存原始标准输出的文件描述符 int file_fd; // 用于重定向输出的文件描述符 // 1 . 保存原始的标准输出 (stdout, 文件描述符 1 ) saved_stdout = dup(STDOUT_FILENO); if (saved_stdout == -1 ) { perror("dup saved_stdout" ); exit (EXIT_FAILURE); } printf("Saved original stdout to fd %d\n" , saved_stdout); // 2 . 打开一个文件用于重定向 file_fd = open("output_redirection.txt" , O_WRONLY | O_CREAT | O_TRUNC, 0644 ); if (file_fd == -1 ) { perror("open file for redirection" ); close(saved_stdout); // 清理 exit (EXIT_FAILURE); } printf("Opened file 'output_redirection.txt' with fd %d\n" , file_fd); // 3 . 将标准输出重定向到文件 // 方法一:使用 dup2 if (dup2(file_fd, STDOUT_FILENO) == -1 ) { perror("dup2 redirect stdout" ); close(file_fd); close(saved_stdout); exit (EXIT_FAILURE); } printf("Standard output redirected to file.\n" ); // 注意:从现在开始,printf 的输出将写入到文件中,而不是终端! // 4 . 输出一些内容到文件 printf("This line goes to the file.\n" ); printf("This line also goes to the file.\n" ); // 5 . 恢复标准输出 // 使用 dup2 将保存的原始 stdout 描述符复制回 STDOUT_FILENO if (dup2(saved_stdout, STDOUT_FILENO) == -1 ) { perror("dup2 restore stdout" ); // 清理 close(file_fd); close(saved_stdout); exit (EXIT_FAILURE); } printf("Standard output restored to terminal.\n" ); // 注意:从现在开始,printf 的输出将重新显示在终端上! // 6 . 再输出一些内容到终端 printf("This line goes back to the terminal.\n" ); // 7 . 关闭所有打开的文件描述符 // close(file_fd) 实际上关闭了文件表项的一个引用 // dup2 在重定向时已经关闭了原来的 STDOUT_FILENO 对应的文件表项引用 // 所以这里只需要关闭 file_fd 和我们自己保存的 saved_stdout if (close(file_fd) == -1 ) { perror("close file_fd" ); } // 关闭 saved_stdout 也会关闭它指向的文件表项(即原始的终端 stdout) if (close(saved_stdout) == -1 ) { perror("close saved_stdout" ); } printf("All file descriptors closed.\n" ); return 0 ; }
代码解释:
调用 dup(STDOUT_FILENO) 创建原始标准输出(文件描述符 1)的一个副本,并将其保存在 saved_stdout 中。这个副本让我们可以稍后恢复标准输出。
使用 open 创建或打开一个名为 output_redirection.txt 的文件,用于接收重定向的输出。
调用 dup2(file_fd, STDOUT_FILENO)。这会:
关闭当前的 STDOUT_FILENO(文件描述符 1)。
将 file_fd 复制一份,并使这个新副本的号码为 1 (即 STDOUT_FILENO)。
现在,文件描述符 1 和 file_fd 都指向 output_redirection.txt 文件。
执行 printf。因为标准输出已经被重定向,所以这些内容会写入到 output_redirection.txt 文件中。
调用 dup2(saved_stdout, STDOUT_FILENO) 来恢复标准输出。这会将之前保存的原始终端输出描述符复制回文件描述符 1。
再次执行 printf。现在标准输出已经恢复,内容会显示在终端上。
最后,使用 close 关闭所有打开的文件描述符。
示例 2:dup2 自动关闭目标文件描述符 这个例子重点演示 dup2 在目标文件描述符已打开时自动关闭它的行为。
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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main () { int fd1, fd2; char buffer[ ;100 ]; ssize_t bytes_read; fd1 = open ("file1.txt" , O_WRONLY | O_CREAT | O_TRUNC, 0644 ); if (fd1 == -1 ) { perror ("open file1.txt" ); exit (EXIT_FAILURE); } write (fd1, "Content of file 1\n" , 18 ); fd2 = open ("file2.txt" , O_WRONLY | O_CREAT | O_TRUNC, 0644 ); if (fd2 == -1 ) { perror ("open file2.txt" ); close (fd1); exit (EXIT_FAILURE); } write (fd2, "Content of file 2\n" , 18 ); printf ("Initially:\n" ); printf (" fd1 points to 'file1.txt' (fd=%d)\n" , fd1); printf (" fd2 points to 'file2.txt' (fd=%d)\n" , fd2); printf ("\nCalling dup2(fd1, fd2)...\n" ); if (dup2 (fd1, fd2) == -1 ) { perror ("dup2(fd1, fd2)" ); close (fd1); close (fd2); exit (EXIT_FAILURE); } printf ("After dup2(fd1, fd2):\n" ); printf (" fd1 still points to 'file1.txt' (fd=%d)\n" , fd1); printf (" fd2 now ALSO points to 'file1.txt' (fd=%d)\n" , fd2); printf (" 'file2.txt' has been closed automatically.\n" ); write (fd2, "Data written via fd2 after dup2\n" , 32 ); close (fd1); close (fd2); printf ("\nContents of 'file1.txt' after operations:\n" ); int read_fd = open ("file1.txt" , O_RDONLY); if (read_fd != -1 ) { while ((bytes_read = read (read_fd, buffer, sizeof (buffer) - 1 )) > 0 ) { buffer[ ;bytes_read] = '\0' ; printf ("%s" , buffer); } close (read_fd); } printf ("\nContents of 'file2.txt' (should be empty or just initial content if not truncated):\n" ); read_fd = open ("file2.txt" , O_RDONLY); if (read_fd != -1 ) { while ((bytes_read = read (read_fd, buffer, sizeof (buffer) - 1 )) > 0 ) { buffer[ ;bytes_read] = '\0' ; printf ("%s" , buffer); } close (read_fd); } if (bytes_read == 0 ) { printf ("(file2.txt is empty)\n" ); } return 0 ; }
代码解释:
打开两个文件 file1.txt 和 file2.txt,分别得到文件描述符 fd1 和 fd2。
向两个文件写入不同的初始内容。
调用 dup2(fd1, fd2)。关键点在于:
现在,fd1 和 fd2 都指向 file1.txt。
向 fd2 写入数据,数据会出现在 file1.txt 中,证明了 fd2 现在确实指向 file1.txt。
关闭 fd1 和 fd2。由于它们指向同一个文件表项,文件只会在最后一个引用关闭时才真正关闭。
读取 file1.txt 和 file2.txt 的内容来验证操作结果。file1.txt 应该包含所有写入的内容,而 file2.txt 可能为空(因为它在 dup2 时被关闭了,如果它之前的内容没有被 O_TRUNC 重新截断,则可能还保留着)。
示例 3:dup 选择最小可用文件描述符 这个例子演示 dup 如何选择最小的可用文件描述符。
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 int main() { int fd, new_fd1, new_fd2; // 1 . 打开一个文件 fd = open("test_dup.txt" , O_WRONLY | O_CREAT | O_TRUNC, 0644 ); if (fd == -1 ) { perror("open" ); exit (EXIT_FAILURE); } printf("Original file descriptor: %d\n" , fd); // 2 . 复制文件描述符 (使用 dup) new_fd1 = dup(fd); if (new_fd1 == -1 ) { perror("dup 1" ); close(fd); exit (EXIT_FAILURE); } printf("First dup() returned new fd: %d\n" , new_fd1); // 3 . 再次复制 new_fd2 = dup(fd); if (new_fd2 == -1 ) { perror("dup 2" ); close(fd); close(new_fd1); exit (EXIT_FAILURE); } printf("Second dup() returned new fd: %d\n" , new_fd2); // 4 . 写入数据到所有描述符,验证它们指向同一文件 write(fd, "Written via fd\n" , 15 ); write(new_fd1, "Written via new_fd1\n" , 20 ); write(new_fd2, "Written via new_fd2\n" , 20 ); printf("Data written via all three file descriptors.\n" ); // 5 . 关闭所有描述符 close(fd); close(new_fd1); close(new_fd2); printf("All file descriptors closed.\n" ); return 0 ; }
代码解释:
打开一个文件,得到文件描述符 fd(假设为 3)。
调用 dup(fd)。因为 0, 1, 2 (stdin, stdout, stderr) 通常已被占用,所以 dup 会返回下一个最小的可用描述符,比如 4。
再次调用 dup(fd)。现在 3, 4 已被占用,所以会返回 5。
向 fd, new_fd1, new_fd2 写入数据,验证它们都写入了同一个文件。
关闭所有文件描述符。
总结:
dup 和 dup2 是用于复制文件描述符的强大工具,它们在实现输入/输出重定向、保存和恢复标准流、以及在进程管理(如 fork 后)中非常有用。理解它们的关键在于掌握新旧描述符共享文件表项的特性,以及 dup2 自动关闭目标描述符的行为。
相关阅读