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 建立的新描述符不會繼承 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 緩衝區與核心的描述符操作彼此獨立。遵守這些檢查,就能更可靠地替換與清理描述符,同時避免假設描述符編號固定。