向标准输入(stdin)写入数据并从标准输出(stdout)读取数据 (UNIX/LINUX/C编程)

26
我在做一个作业,其中一个程序接受文件描述符作为参数(通常是通过exec调用从父进程中获得),从文件中读取并写入文件描述符。在我的测试中,我意识到如果将0、1或2作为文件描述符,则该程序可以从命令行正常工作且不会出错。 这很合理,但我可以将内容写入stdin并在屏幕上显示。这是什么原因?我一直以为stdin / stdout上有保护措施,肯定不能fprintf到stdin或从stdout获取fgets。
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
    char message[20];
    read(STDOUT_FILENO, message, 20);
    write(STDIN_FILENO, message, 20);

    return 0;
}

1
请不要混淆文件句柄和文件描述符。您可能无法将 fprintf 写入 stdin(尽管我在标准中找不到任何明确禁止它的内容),但这对于您是否可以向文件描述符 0 写入 write 没有任何影响。 - paxdiablo
1
它们的区别是什么?它们不是完成同样的任务吗? - Tim
6
一根胡萝卜和一根棍子可以完成同样的任务,但它们并不是相同的东西,询问你友好的邻居驴就知道了 :-) 句柄是标准C的,描述符是POSIX的。 - paxdiablo
5个回答

22

试图在只读文件中写入或反之亦然,会导致writeread返回-1并失败。在这种特定的情况下,标准输入和标准输出实际上是同一个文件。本质上,在程序执行之前(如果您没有进行任何重定向),shell会执行以下操作:

  if(!fork()){
       <close all fd's>
       int fd = open("/dev/tty1", O_RDWR);
       dup(fd);
       dup(fd);
       execvp("name", argv);
  }
所以,stdin、out和err都是同一个文件描述符的副本,该文件描述符被打开用于读取和写入。

6
更正,这并不是通过shell完成的,而是通过终端(模拟器)完成的。Shell会像任何其他进程一样继承其stdin/stdout句柄。 - Vladimir Panteleev

3
read(STDIN_FILENO, message, 20); 
write(STDOUT_FILENO, message, 20);

应该可以正常工作。请注意 - stdout 可能与 stdin 不同(即使在命令行上)。您可以将另一个进程的输出作为 stdin 填充到您的进程中,或者安排 stdin / stdout 为文件。

fprintf / fgets 具有缓冲区 - 从而减少系统调用的数量。


2
最好的猜测 - stdin 指向输入来自哪里,即终端,而 stdout 指向输出应该去向哪里,即终端。由于它们都指向相同的地方,因此它们在这种情况下是可互换的吗?

2
但它们可能不是您的终端。 - Ed Heal
@Ed 然后它将从/写入任何 stdin/stdout 指向的内容。这是很多关于这个/那个的内容。 - Avery3R
这是一个有趣的思考方式,但对于在线翻译工具来说并不适用。 - The Empty String Photographer
这是一个有趣的思考方式,但对于在线翻译工具来说并不适用。 - undefined

2

很有可能文件描述符0、1和2都同时打开了读写(实际上它们都指向同一个底层的“打开文件描述符”),这种情况下你所做的操作是可行的。但据我所知,没有保证,因此也可能不起作用。我相信POSIX在某个地方规定,如果stderr在shell调用程序时连接到终端,则应该可读可写,但我找不到参考资料。

一般来说,除非你要从终端读取密码并且stdin已被重定向(不是tty),否则我建议不要从stdout或stderr读取任何内容。而且我建议永远不要写入stdin——这是危险的,你可能会覆盖用户没有预期写入的文件!


1
如果您在UNIX上运行程序
myapp < input > output

您可以打开/proc/{pid}/fd/1并从中读取,打开/proc/{pid}/fd/0并向其中写入,例如将output复制到input。(可能有更简单的方法,但我知道它有效)

如果你想的话,你可以做任何让人困惑的事情。 ;)


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