【问题标题】:Not able to read input from stdin/STDIN_FILENO in C?无法从 C 中的 stdin/STDIN_FILENO 读取输入?
【发布时间】:2018-05-03 06:10:00
【问题描述】:

我有这个命令行参数 -

cat file_name | ./a.out 

问题不是从 C 程序中的 cat 命令读取,因为我们可以使用 read()fgets()fgetc()我无法使用 fgets 获取用户的输入。

这是我的示例代码

while(fgets(buffer, BUFSIZ, stdin ) != NULL )
    puts( buffer ); // Here I have tried strtok( buffer, "\n" ) too.
memset( buffer, 0, BUFSIZ );`

问题出在这一行之后,它没有像下面那样要求输入不起作用-

puts("Name: ");
fgets( buffer, BUFSIZ, stdin );

帮我看看这里出了什么问题?

【问题讨论】:

  • 似乎很合理,因为stdin 现在连接到cat 命令的输出,然后 fgets() 将从其输出中读取,它无法读取用户在终端输入的内容.
  • 你知道解决办法吗? @nos
  • @Devesh.Pratap 解决方案是,如果您需要从用户和文件中读取输入,您不能将文件的内容通过管道传输到您的程序中,而是为您的程序提供一个参数并在您自己的程序中打开文件。您可以将其运行为 ./a.out file_name
  • 坚持按设计不可能的事情是没有帮助的。在问题中大喊大叫也不是。最好退后一步,重新考虑您的需求——为什么您认为您需要它?谷歌搜索“XY 问题”。
  • 那是因为没有办法。只有一个stdin,如果您在shell 中进行重定向,shell 会将其连接到另一个命令的输出,就这么简单。注意“这是不可能的”在这里是一个正确的答案。因此,如果您需要进一步的帮助,请说明该要求应该实现什么,我们可以帮助您找到不同的方法。 (而且,为了记录,用全部大写是“大喊大叫”,即使你不知道......)

标签: c stdin stdio


【解决方案1】:

当您执行cat file_name | ./a.out 时,程序的标准输入将绑定到一个管道,该管道将其链接到cat 的输出。您的程序将永远无法看到用户输入 - 它将到达的流已被上述管道替换。

请注意,我怀疑通过一些可怕的 POSIX 特定的诡计,您可能能够直接在 tty 设备上重新打开它,但这只是糟糕的设计。如果您需要同时读取文件 接受交互式用户输入,只需接受该文件作为命令行参数并使用 stdin 与用户进行交互。

编辑

这是一个可以尝试的 Unix 特定的组合示例,假设进程仍然有一个控制终端。在阅读了所有原始标准输入后,我打开/dev/tty(这是进程的控制终端)并将stdin重新链接到它。

免责声明:仅供娱乐,请勿当真

#include <stdio.h>
#include <stdlib.h>

void die(const char *msg) {
    fprintf(stderr, "%s\n", msg);
    fputs(msg, stderr);
    exit(1);
}

int main() {
    /* Read all of stdin and count the bytes read (just to do something with it) */
    int ch;
    unsigned long count = 0;
    while((ch = getchar())!=EOF) {
        count++;
    }
    printf("Read %lu bytes from stdin\n", count);
    /* Open the controlling terminal and re-link it to the relevant C library FILE *
     * Notice that the UNIX fd for stdin is still the old one (it's
     * surprisingly complex to "reset" stdio stdin to a new UNIX fd) */
    if(freopen("/dev/tty", "r", stdin) == NULL) {
        die("Failed freopen");
    }

    /* Do something with this newly gained console */
    puts("How old are you?");
    fflush(stdout);
    int age = -1;
    if(scanf("%d", &age)!=1) {
        die("Bad input");
    }
    printf("You are %d years old\n", age);
    return 0;
}

(以前我有一个解决方案检查 stderrstdout 是否仍然是控制台,这更像是一个杂物;感谢 @rici 提醒我 POSIX 已经“控制终端”的概念,可通过/dev/tty访问)

【讨论】:

  • 我不能这样做,因为它不是必需的。我需要使用cat 以及线程内的用户获取输入。
  • 这就像问如何恢复已删除的文件;没有解决方案,只有杂乱无章,因为您违背了系统应该如何工作并且您需要的信息已经丢失。您可以在/proc 下找到您的父进程并四处窥探,看看您是否可以找到可能与您相同的 pty(但是如果您开始分离怎么办?);您可以希望您的标准输出或标准错误没有被重定向,并使用它来找出您正在写入的 tty,然后重新打开它以进行输入。你可以尝试很多组合,但它们都是寻找问题的糟糕解决方案。
  • 我添加了一个这样的组合的例子;它有点适用于 Linux,但同样,它主要用于娱乐目的。我永远不会使用这种东西发布任何东西,因为它很脆弱,而且因为它是一个可怕的界面——什么样的脑死程序会主动规避标准输入重定向?
  • @rici:你说得对,我完全忘记了控制终端!这样好多了。
  • @FelixPalmen:啊哈哈,幸运的是我从来不用真正的 Perl,我只接触过它几次,我受到了彻底的创伤。
【解决方案2】:

如果您需要使用stdin 进行用户交互,那么您需要使用不同的文件描述符来读取输入流。

您可以使用特定的预先打开的文件描述符和文档(例如“输入流应该连接到 fd 3”),但通常的方法是接受文件名作为命令行参数。然后,您可以提供一个命名管道作为参数;诸如 Bash 之类的 shell 提供了 进程替换 来简化:

./a.out <(cat file_name)

当它以交互方式运行时,stdin 仍然连接到终端,并且可以与来自连接命令的流同时使用。

(显然,如果命令实际上 is cat 带有单个参数,那么您可以只提供文件名本身作为参数,但我假设这是一个占位符管道)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-27
    • 2012-03-15
    • 1970-01-01
    • 1970-01-01
    • 2012-08-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多