如何正确地fread和fwrite和管道

问题描述 投票:0回答:2

我有这个代码作为两个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没有收到任何东西。

怎么做得好呢?

c linux pipe eof stdio
2个回答
1
投票

首先,要说我认为你在缓冲方面遇到的问题多于效率问题。这是第一次处理stdio包时的常见问题。

其次,从输入到输出的简单数据复制器的最佳(和最简单)实现是以下片段(从K&R第一版复制)。

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

(好吧,不是文字副本,因为那里,K&R使用stdinstdout作为FILE*描述符,他们使用更简单的getchar();putchar(c);调用。)当你试图做得比这更好时,通常你会产生一些错误的假设,如缺乏缓冲或系统调用次数的谬误。

当标准输出是一个管道时,stdio执行完全缓冲(实际上,它总是完全缓冲,除非文件描述符将true提供给isatty(3)函数调用),所以你应该这样做,如果你想尽快看到输出它至少在某些时候没有输出缓冲(类似于setbuf(out, NULL);fflush())你的输出,所以当你在输入中等待更多数据时,它不会在输出中得到缓冲。

看起来是你看到less(1)程序的输出不可见,因为它被缓存在程序的内部。而这正是发生的事情...假设您提供程序(尽管处理单个字符,正在进行完全缓冲),在完整输入缓冲区(BUFSIZ字符)被填充之前不会得到任何输入它。然后,许多单个fgetc()调用在循环中完成,许多fputc()调用在循环中完成(正好BUFSIZ调用每个)并且缓冲区在输出处填充。但是这个缓冲区没有被写入,因为它需要一个char来强制刷新。因此,在获得前两个BUFSIZ数据块之前,您不会收到任何写入less(1)的内容。

一个简单而有效的方法是在fputc(c, out);之后检查char是\n,在这种情况下用fflush(out);刷新输出,所以你将一次写一行输出。

fputc(c, out);
if (c == '\n') fflush(out);

如果你不做某事,缓冲是在BUFSIZ块中进行的,通常,在输出端有这么大量的数据之前。并且始终记住fclose()的东西(好吧,这是由stdio处理的),否则你可能会丢失输出以防你的进程被中断。

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

while ((c = fgetc(input))  !=  EOF) {
    fputc(c, output);
    if (c == '\n') fflush(output);
}
fclose(input);
fclose(output);

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

顺便说一下,做一个char的fread()fwrite(),是浪费时间和一种让事情复杂化(并且容易出错)的方法。一个字符的fwrite()不会避免使用缓冲区,所以你不会比使用fputc(c, output);获得更多的性能。

BTW(bis)如果你想做自己的缓冲,不要调用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);
}

一旦你产生了一行输出文本,你就会得到less(1)的工作。

你究竟想做什么? (这将是很好的知道,因为你似乎正在重新发明cat(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()case。 (getc()将(通常)将是一个宏,避免函数调用开销)。 [只是看一下生成的程序集。

© www.soinside.com 2019 - 2024. All rights reserved.