如何正确地从管道读取数据和向管道写入数据

时间:2019-04-13 18:38:03

标签: c linux pipe eof stdio

我有这段代码,它充当两个shell调用之间的管道。

它从管道读取,然后写入另一个管道。

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


#define BUFF_SIZE (0xFFF)

/*
 *  $ cat /tmp/redirect.txt |less
 */
int main(void)
{
    FILE    *input;
    FILE    *output;
    int     c;
    char    buff[BUFF_SIZE];
    size_t  nmemb;

    input   = popen("cat /tmp/redirect.txt", "r");
    output  = popen("less", "w");
    if (!input || !output)
        exit(EXIT_FAILURE);

#if 01
    while ((c = fgetc(input))  !=  EOF)
        fputc(c, output);
#elif 01
    do {
        nmemb   = fread(buff, 1, sizeof(buff), input);
        fwrite(buff, 1, nmemb, output);
    } while (nmemb);
#elif 01
    while (feof(input) != EOF) {
        nmemb   = fread(buff, 1, sizeof(buff), input);
        fwrite(buff, 1, nmemb, output);
    }
#endif
/*
 * EDIT: The previous implementation is incorrect:
 * feof() return non-zero if EOF is set
 * EDIT2:  Forgot the !.  This solved the problem.
 */
#elif 01
    while (feof(input)) {
        nmemb   = fread(buff, 1, sizeof(buff), input);
        fwrite(buff, 1, nmemb, output);
    }
#endif

    pclose(input);
    pclose(output);

    return  0;
}

我希望它高效,所以我想用fread()fwrite()来实现它。我尝试过3种方式。

第一个是通过fgetc()fputc()实现的,因此速度非常慢。但是,它可以正常工作,因为它会检查EOF,因此它将等到cat(或我使用的任何shell调用)完成其工作。

第二个速度更快,但是我担心我不检查EOF,所以是否有任何时刻管道为空(但是shell调用尚未完成,所以可能不会将来为空),它将关闭管道并结束。

第三个实现是我想做的,它相对有效(所有文本都由less接收),但是由于某种原因,它被卡住了并且没有关闭管道(看起来像这样)永远不会得到EOF)。

编辑:第三种实现是错误的。第四种试图解决该错误,但是现在less收不到任何东西。

这应该如何正确完成?

2 个答案:

答案 0 :(得分:1)

首先,要说的是,我认为您遇到的问题更多是缓冲问题,而不是效率问题。第一次处理stdio软件包时,这是一个常见问题。

第二,从输入到输出的简单数据复印机的最佳(也是最简单)实现是以下代码段(从K&R的第一版复制)。

while((c = fgetc(input)) != EOF) 
    fputc(c, output);

(好吧,不是普通的副本,K&R使用stdinstdout作为FILE*描述符,而他们使用更简单的getchar();和{{1} }调用。)当您尝试做得更好时,通常会因缺乏缓冲或系统调用数量的谬误而产生一些错误的假设。

putchar(c);在标准输出为管道时进行完全缓冲(实际上,除非文件描述符将stdio赋予{{1},否则它总是进行完全缓冲}函数调用),因此,在您希望尽快看到输出的情况下,应该这样做,至少不要对输出进行缓冲(使用trueisatty(3)这样的输出)在某个时候输出,因此当您等待输入中的更多数据时,它不会在输出中得到缓冲。

这似乎是您看到setbuf(out, NULL);程序的输出不可见,因为它被缓冲在程序的内部。这就是正在发生的事情...假设您提供程序(尽管处理了单个字符,但正在执行完全缓冲)直到完全输入缓冲区(fflush()个字符)都没有任何输入已经被喂饱了。然后,许多单个less(1)调用是在一个循环中完成的,而许多BUFSIZ调用是在一个循环中完成的(每个精确地fgetc()调用),并且缓冲区被填充输出。但是不会写入此缓冲区,因为它还需要一个char来强制进行刷新。因此,在获得前两个fputc()数据块之前,您什么都不会写入BUFSIZ

一种简单有效的方法是在BUFSIZ之后检查字符是否为less(1),并在这种情况下用fputc(c, out);刷新输出,因此您将写一行一次输出。

\n

如果您不执行任何操作,则缓冲将在fflush(out);块中进行,并且通常在输出端有如此大量的数据之前不会进行缓冲。并且请记住始终fputc(c, out); if (c == '\n') fflush(out); 处理(好吧,这由BUFSIZ处理),否则,如果您的进程被中断,您可能会丢失输出。

恕我直言,您应该使用的代码是:

fclose()

为获得最佳性能,同时不会不必要地阻塞缓冲区中的输出数据。

BTW用一个字符进行stdiowhile ((c = fgetc(input)) != EOF) { fputc(c, output); if (c == '\n') fflush(output); } fclose(input); fclose(output); 既浪费时间,又使事情复杂很多(并且容易出错)。一个字符的fread()不会避免使用缓冲区,因此您不会比使用fwrite()获得更多的性能。

BTW(bis)如果要进行自己的缓冲,不要调用fwrite()函数,只需对常规系统文件描述符使用fputc(c, output);stdio调用。一个好的方法是:

read(2)

但这只会在缓冲区中完全充满数据或没有更多数据时唤醒您的程序。

如果您希望在一行数少的情况下立即将数据馈送到write(2),则可以使用以下命令完全禁用输入缓冲区:

int input_fd = fileno(input); /* input is your old FILE * given by popen() */
int output_fd = fileno(output);

while ((n = read(input_fd, your_buffer, sizeof your_buffer)) > 0) {
    write(output_fd, your_buffer, n);
}
switch (n) {
case 0: /* we got EOF */
    ...
    break;
default: /* we got an error */
    fprintf(stderr, "error: read(): %s\n", strerror(errno));
    ...
    break;
} /* switch */

只要生成一行输出文本,您就可以开始less(1)的工作。

您到底想做什么? (很高兴知道这一点,因为您似乎正在重新发明setbuf(input, NULL); int c; /* int, never char, see manual page */ while((c == fgetc(input)) != EOF) { putc(c, output); if (c == '\n') fflush(output); } 程序,但功能有所减少)

答案 1 :(得分:0)

最简单的解决方案:


while (1) {
    nmemb = fread(buff, 1, sizeof buff, input);
    if (nmemb < 1) break; 
    fwrite(buff, 1, nmemb, output);
}

类似地,对于getc()情况:


while (1) {
    c = getc(input);
    if (c == EOF) break;
    putc(c, output);
}

fgetc()替换为getc()将获得与fread()情况相同的性能。 (getc()(通常)是宏,避免了函数调用的开销)。 [只看一下生成的程序集。