HOWTO · C
在 C 語言中使用檔案重定向
本文演示瞭如何在 C 語言中使用檔案重定向。
本頁內容
若要在命令列將 C 程式的輸入或輸出重新導向,請使用 Shell 運算子 <、> 和 >>。Shell 會將程式的標準串流連接到檔案;C 程式碼則可以用 fopen 開啟具名檔案,或用 freopen 更換標準串流。這些都是處理資料的方式,但彼此執行的操作不同。
認識 C 語言中的檔案重新導向
C 程序啟動時會有三個標準串流:stdin 用於輸入、stdout 用於一般輸出,而 stderr 用於診斷訊息。Shell 通常會將它們連接到終端機。啟動程式時,重新導向會改變這些連接方式,因此程式仍可呼叫 fgets、printf 或 fprintf(stderr, ...),無須知道串流連接的是終端機還是檔案。
相較之下,fopen 會傳回另一個 FILE *,讓程式碼直接讀取或寫入該檔案。若程式必須在多個檔案中選擇、同時處理多個檔案,或控制檔案的使用期間,就適合使用它。若要讓呼叫者決定這次執行的輸入或輸出目的地,則適合使用 Shell 重新導向。
使用 > 和 » 重新導向程式輸出
以下小程式會將一行文字寫入 stdout:
執行此範例:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
#include <stdio.h>
int main(void) {
puts("Report complete");
return 0;
}
程式會將該行文字寫入 stdout。這個範例沒有附上執行時的輸出紀錄;程式碼及其中顯示的文字維持英文。將程式編譯為 report 後,執行 ./report > report.txt 可建立或取代 report.txt。若要改為附加內容,請使用 ./report >> report.txt。> 會在程式啟動前截斷既有的目的檔案;>> 則會從檔案末尾寫入。Shell 語法與可用的編譯器命令會因平台而異。
重新導向目的地的開啟工作由 Shell 負責。若開啟失敗,Shell 會回報問題,通常也不會啟動程式。由於目的檔案不是由程式自行開啟,程式無法將此情況當成 fopen 失敗來偵測。若要將診斷訊息另存到不同檔案,請使用 ./report > report.txt 2> errors.txt;2> 會重新導向慣例上用於 stderr 的檔案描述符。
使用 < 重新導向程式輸入
當輸入來源應由呼叫者決定時,請從 stdin 讀取。以下程式會逐行讀取,直到重新導向的檔案到達檔案結尾:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
#include <stdio.h>
int main(void) {
char line[128];
while (fgets(line, sizeof line, stdin) != NULL) {
if (fputs(line, stdout) == EOF) {
perror("writing stdout");
return 1;
}
}
if (ferror(stdin)) {
perror("reading stdin");
return 1;
}
return 0;
}
將程式編譯為 copy-input,接著執行 ./copy-input < input.txt。fgets 最多讀取比緩衝區容量少一個字元的內容,並加入結尾的空字元;若讀到換行字元,也會保留它。因此,較長的一行可能會分成多段回傳。迴圈結束後請檢查 ferror,以區分讀取失敗與正常到達檔案結尾。範例的行為依照所示的 C 串流操作;此處未提供觀察到的輸出。程式碼及其中顯示的文字維持英文。
在 C 程式中重新導向標準串流
若目的地必須由程式自行決定,可以用 freopen 重新開啟標準串流。它會接收路徑、"w" 或 "a" 之類的模式,以及要重新開啟的串流。例如,freopen("run.log", "a", stdout) 會讓後續的標準輸出附加到 run.log。寫入前請檢查其回傳值:重新開啟失敗時會傳回 NULL。這會變更程序的串流;fopen 則會提供另一個串流,而不會影響 stdout。
若重新導向是程式本身行為的一部分,就適合使用這種方式。若應由呼叫者決定目的地,請保持 stdout 不變,改用 > 或 >>。freopen 是標準 C 函式,但重新開啟失敗後的串流狀態細節可能因實作而異;請妥善處理 NULL,不要在重新導向未成功時仍假設它已成功。
處理錯誤並選擇合適的方法
請檢查每個可能失敗的操作。使用回傳的串流前,先檢查 fopen 和 freopen;檢查 fprintf 或 fputs 等輸出函式是否失敗;若必須確認緩衝的輸出已寫入檔案,也要檢查 fclose。在輸入迴圈中,fgets 傳回 NULL 可能表示已到檔案結尾,也可能是讀取錯誤;請檢查 ferror 來區分兩者。若目前的 errno 對應到某項失敗,perror 可以回報作業系統提供的訊息。
不要根據檔案回報的位元組長度來決定一行資料的緩衝區大小:輸入可能來自管線或終端機,而一行也可能比選定的緩衝區更長。若可以接受分段處理,使用固定大小的緩衝區並重複呼叫 fgets 即可。若要讀取長度不受限的邏輯行,可隨著資料分段到達逐步擴大緩衝區,同時檢查配置失敗與算術範圍限制;也可以使用平台專屬的逐行讀取功能,並清楚說明其可攜性限制。
使用 Shell 重新導向來連接這次執行的標準串流;使用 fopen 在 C 中管理額外檔案;若程式需要自行取代標準串流,則使用 freopen。一般輸出重新導向時,請將診斷訊息留在 stderr,並在執行該操作的層級檢查失敗。