【问题标题】:How to pipe data to a program which calls scanf() and read() in Linux如何将数据通过管道传输到 Linux 中调用 scanf() 和 read() 的程序
【发布时间】:2014-11-02 08:55:51
【问题描述】:

我有一个如下所示的 C 程序:

#include <stdio.h>
#include <unistd.h>

int main()
{
    int n;
    char str[16];
    scanf("%d", &n);
    printf("n: %d\n", n);

    int count = read(STDIN_FILENO, str, 16);
    printf("str: %s\n", str);
    printf("read %d bytes\n", count);
}

如果我使用类似的命令将数据传送到该程序中

(echo -en '45\n'; echo -en 'text\n') | ./程序

只有scanf() 实际读取数据。 read() 只读取 0 个字节。

换句话说,程序输出

n: 45
字符串:
读取 0 个字节

如何将数据通过管道传输到scanf()read()

编辑:对不起,我应该澄清一下:我正在寻找一种方法来做到这一点,而无需修改源代码。感谢无论如何回答和评论的人。

【问题讨论】:

  • 您可以先在代码中添加:#include
  • 需要检查 scanf() 的返回码,以确保该操作实际上将值输入到 'n'。
  • 注意:read() 不会在输入的末尾放置一个空字符。所以代码必须这样做,使用 read() 调用的返回值来确定放置空字符的位置。否则,printf 将直接读取实际的 str 内容,直到它(最终)遇到 null char 或导致 seg 错误
  • 如果实际上有 16 个或更多字节可供读取,则读取将在 16 个字节处停止,但 str 数组中将没有空间来附加空字节。建议将 str 的大小更改为 17,然后将数组预设为所有空字节。这将确保 str 的内容被正确终止(预先设置所有字节,而不仅仅是最后一个字节)
  • scanf 停在 '\n' 处,而不是要读取的行中的第一个字符。

标签: c linux unix pipe


【解决方案1】:
#include <stdio.h>
#include <unistd.h>

int main()
{
    int n;
    char str[16];
    setbuf(stdin, NULL); // Ensure that there's no buffering for stdin
    scanf("%d", &n);
    printf("n: %d\n", n);

    int count = read(STDIN_FILENO, str, 16);
    printf("str: %s\n", str);
    printf("read %d bytes\n", count);
}

正如前面的答案所说, scanf 会导致您的问题,因为它使用缓冲区。因此,您可以通过调用setbuf(stdin,NULL) 来确保它不会出现。

【讨论】:

    【解决方案2】:

    完全不建议对同一个文件描述符同时使用文件描述符函数 (read) 和流函数 (scanf)。使用FILE *(即fread/fprintf/scanf/...)的函数正在缓冲数据,而使用文件描述符(即read/write/...)的函数不使用这些缓冲区。在您的情况下,修复程序的最简单方法是使用fread 而不是read。该程序可能如下所示:

    #include <stdio.h>
    #include <unistd.h>
    
    int main() {
      int n;
      char str[16];
      scanf("%d", &n);
      printf("n: %d\n", n);
    
      int count = fread(str, 16, 1, stdin);
      printf("str: %s\n", str);
      printf("read %d bytes\n", count);
    }
    

    在您的原始示例中,scanf 已提前读取输入并将其存储在其缓冲区中。因为输入很短并且可以立即使用,所以它被完全读取到缓冲区中,而您对 read 的调用没有更多可读取的内容。

    当您直接从终端输入输入时不会发生这种情况,因为scanf 在从终端设备读取时不会缓冲超过一行的数据。如果您在管道输入中创建时间暂停(例如通过命令),也不会发生这种情况:

    (echo -en '45\n'; sleep 1; echo -en 'text\n') | ./program
    

    【讨论】:

    • 有没有办法在不修改源代码的情况下做到这一点?可能有某种方法可以确保 scanf 不会缓冲所有输入?
    • @IGNUcius:缓冲的不是调用,而是用于扫描/读取的结构:FILE
    • 可能值得更明确地说明底层概念是问题所在:缓冲 I/O 与非缓冲 I/O
    • @IGNUcius 我不知道正确的解决方案。您可以在将数据发送到管道时创建时间延迟,或者在创建管道时使用 fcntl 来破解管道的传出端并使 scanf 认为他正在从终端读取。
    猜你喜欢
    • 2011-01-14
    • 2014-12-23
    • 2014-02-03
    • 1970-01-01
    • 2011-08-09
    • 2017-03-22
    • 2015-10-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多