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;并在实际执行操作的那一层检查失败。