重定向C语言中的标准输入和输出

77

我希望重新打开stdinstdout(或者当我在它时,也许是stderr)文件句柄,这样未来对printf()putchar()puts()的调用将会转到一个文件,而对getc()等的调用将会从一个文件中进行。

1)我不想永久性地失去标准输入/输出/错误。我可能稍后还要重用它们。

2)我不想打开新的文件句柄,因为这些文件句柄要么需要经过很多传递,要么是全局的(让人毛骨悚然)。

3)如果可以避免,我不想使用任何open()fork()或其他系统相关函数。

所以,基本上这样做可行吗:

stdin = fopen("newin", "r");

如果真的有必要,那么我该如何获取stdin的原始值呢?我需要将其存储在FILE *中并稍后再获取吗?


1
请注意,stdinstdoutstderr是全局变量。 - Alexis Wilke
8个回答

97

为什么要使用freopen()? C89规范在关于<stdio.h>的一个尾注中给出了答案:

116. freopen函数的主要用途是更改与标准文本流(stderr, stdin, 或stdout)相关联的文件,因为这些标识符不需要是可修改的左值,可以将fopen函数返回的值分配给它们。

freopen经常被误用,例如:stdin = freopen("newin", "r", stdin);。这与fclose(stdin); stdin = fopen("newin", "r");一样不具有可移植性。两个表达式都试图对stdin进行赋值,但无法保证其可赋值。

使用freopen的正确方式是省略赋值:freopen("newin", "r", stdin);


2
@AbiusX 我不确定这种方法有多少可移植性,但在 POSIX 环境下,您可以使用另一种方式——通过使用 dup2() 更改底层句柄的含义来实现:http://linux.die.net/man/3/fflush。这将使您能够在替换之前复制 STDOUT 句柄,然后可以进行复制。只需记得使用 fflush()。这里有一个使用它来 fork 的示例: https://dev59.com/GGox5IYBdhLWcg3wIRD3 - Philip Couling
对于那些想要在重新打开标准描述符之前关闭它的人,有一个在msvc2015 UCRT中测试过的解决方案,使用fclose+_close+freopen序列:const int stdin_fileno = _fileno(stdin); fclose(stdin); if (stdin_fileno < 0) _close(STDIN_FILENO); /* may reallocate console here */; freopen("CONIN$", "r", stdin); /* use _get_osfhandle + SetStdHandle for not console application */。这是有意义的,因为freopen不会重用已分配的描述符并分配一个新的描述符。因此,为了强制它重用描述符,您必须首先将其关闭。 - Andry
当您已经拥有来自管道的FILE*且该文件在文件系统上没有路径时,您该如何处理? - v.oddou

17

我想你正在寻找类似于freopen()的东西。


stdin = freopen("newin", "r", stdin); 和 fclose(stdin); stdin = fopen("newin", "r"); 之间有区别吗? - Chris Lutz
fopen打开文件对象,而freopen将数据流指向文件对象。从页面上来看,“此函数特别适用于将预定义的流(如stdin、stdout和stderr)重定向到特定文件”。 - John T
1
除了“stdin = freopen(”/ dev / tty“,”r“);”之类的方式外,是否有重新打开stdin / stdout以恢复其原始值的方法? - Chris Lutz
1
你可以使用fclose函数来关闭指向文件的流。例如,fclose(stdout); - John T
1
澄清:在语法上将stdin赋值,例如stdin = fopen(...)stdin = freopen(...)是不可移植的,因为stdin允许是一个宏。 stdio.h可能包含#define stdin __builtin_get_stdin(),当然你不能将其放在赋值的左侧。@ChrisLutz,使用freopen的正确方法是简单地if (freopen("newin", "r", stdin) != NULL) ... ---返回值有用于指示打开是否失败,但通常不需要将其分配给任何东西。 - Quuxplusone
显示剩余3条评论

11
这是Tim Post方法的修改版;我使用了/dev/tty而不是/dev/stdout。我不知道为什么它不能与stdout一起工作(它是指向/proc/self/fd/1的链接):
freopen("log.txt","w",stdout);
...
...
freopen("/dev/tty","w",stdout);

通过使用/dev/tty,输出将被重定向到启动应用程序的终端。
希望这些信息对您有用。

10
freopen("/my/newstdin", "r", stdin);
freopen("/my/newstdout", "w", stdout);
freopen("/my/newstderr", "w", stderr);

... do your stuff

freopen("/dev/stdin", "r", stdin);
...
...

这让我感到有些不适应,你想要达到什么目的?

编辑:

请记住,stdin、stdout和stderr对于每个新创建的进程都是文件描述符0、1和2。 freopen() 应该保持相同的fd,只是将新流分配给它们。

因此,确保它实际上正在做你想要做的事情的一个好方法是:

printf("Stdout is descriptor %d\n", fileno(stdout));
freopen("/tmp/newstdout", "w", stdout);
printf("Stdout is now /tmp/newstdout and hopefully still fd %d\n",
   fileno(stdout));
freopen("/dev/stdout", "w", stdout);
printf("Now we put it back, hopefully its still fd %d\n",
   fileno(stdout));

我认为这是 freopen() 的预期行为,正如你所看到的,你仍然只使用了三个文件描述符(和相应的流)。

这将覆盖任何 shell 重定向,因为没有东西可以被 shell 重定向。但是,它很可能会破坏管道。你可能需要确保设置 SIGPIPE 处理程序,以防你的程序发现自己处于管道的阻塞端口(不是 FIFO 管道)。

因此,./your_program --stdout /tmp/stdout.txt --stderr /tmp/stderr.txt 应该很容易地通过 freopen() 实现,并保持相同的实际文件描述符。我不明白的是,为什么你需要在改变它们后把它们放回去?毫无疑问,如果有人传递任一选项,他们希望它在程序终止之前持久化。


我正在尝试添加一种方法,使我的程序能够重定向自己的标准输入和输出,以便那些不熟悉其shell重定向的人使用。 - Chris Lutz
是的,对于这个问题,freopen() 函数将会是你的好帮手。 - Tim Post
这并不像你描述的那样有效。我使用了以下代码:printf("This should be visible on the screen!\n"); freopen("testop.txt", "w", stdout); printf("This should go to testop!\n"); fflush(stdout); fclose(stdout); freopen("/dev/stdout", "w", stdout); printf("And this should go to the screen again!\n"); - VectorVictor
@VectorVictor 奇怪,我下班后会再次运行代码(我确定在2009年发布时已经测试过了)。我将使用现代编译器重新评估。 - Tim Post
嗨Tim。在我的上一篇文章中,我在评论输入方面遇到了麻烦。由于某种原因,它不喜欢复制和粘贴。我想继续列出输出并提供系统详细信息,但发送之前就结束了。前两个指令都如人所料,然而最后一个指令“这应该再次显示在屏幕上”却没有。我正在Mac OS X 10.9.5上的XCode clang 500.2.79上编译。我甚至尝试重新打开/dev/tty的标准输出,但也没有成功。好像一旦重新定义了标准输出,就无法再次使用了。 - VectorVictor

9

os函数dup2()应该提供您所需的内容(如果没有,可以参考确切的内容)。

更具体地说,您可以将stdin文件描述符复制到另一个文件描述符中,对stdin进行其他操作,然后在需要时将其复制回来。

dup()函数复制打开的文件描述符。具体而言,它使用F_DUPFD常量命令值提供了fcntl()函数提供的服务的替代接口,并将第三个参数设置为0。复制的文件描述符与原始文件描述符共享任何锁。

成功时,dup()返回一个新的文件描述符,它与原始文件描述符具有以下相同之处:

  • 相同的打开文件(或管道)
  • 相同的文件指针(两个文件描述符共享一个文件指针)
  • 相同的访问模式(读、写或读/写)

1
问题明确表示不要使用依赖于系统的函数。 - unwind
而且问题明确表示限制打开的流/文件描述符数量。 - Tim Post

3
freopen可以解决简单的问题。如果您还没有读取任何内容,并且愿意使用像dupdup2这样的POSIX系统调用来保留旧的stdin,那么保留旧的stdin不难。如果您已经开始从stdin中读取内容,则一切皆有可能。
也许您可以告诉我们出现此问题的上下文?
我建议您坚持在愿意放弃旧的stdinstdout并因此可以使用freopen的情况下进行操作。

在这种情况下,如果我真的必须这样做,我认为我可以放弃旧的stdin/stdout。我正在尝试为我的程序添加一个选项,使程序能够自己重定向stdin和stdout,以方便那些运行程序但不熟悉其shell的人使用。只是为了好玩。 - Chris Lutz
1
我的学生们肯定在I/O重定向方面遇到了麻烦,所以你的目标听起来很不错。也许你最好使用命令行选项 -i 和 -o 来命名输入和输出文件? - Norman Ramsey
这大致是我的计划(-i和-o已经被占用,但那是一般的接口),我只需要一种实现它的方法(而不必改变所有我的printf()为fprintf()并且到处传递两个文件句柄)。此外,一个用于重定向stderr的标志也会很好。 - Chris Lutz
1
从长远来看,传递文件句柄会让你感到满意,但如果你已经有了遗留代码,freopen()是你的好朋友。 - Norman Ramsey

2
同时,有一个C源代码库可以为您完成所有这些操作,重定向标准输出或标准错误。但是很酷的部分是它允许您为拦截的流分配任意数量的回调函数,然后允许您非常容易地将单个消息发送到多个目的地,如数据库、文本文件等。
此外,它使创建看起来和行为与标准输出和标准错误相同的新流变得非常简单,您可以将这些新流重定向到多个位置。
在*oogle上寻找U-Streams C库。

0

这是最方便、最实用的方法之一。

freopen("dir","r",stdin);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接