我有这段代码,它充当两个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
收不到任何东西。
这应该如何正确完成?
答案 0 :(得分:1)
首先,要说的是,我认为您遇到的问题更多是缓冲问题,而不是效率问题。第一次处理stdio
软件包时,这是一个常见问题。
第二,从输入到输出的简单数据复印机的最佳(也是最简单)实现是以下代码段(从K&R的第一版复制)。
while((c = fgetc(input)) != EOF)
fputc(c, output);
(好吧,不是普通的副本,K&R使用stdin
和stdout
作为FILE*
描述符,而他们使用更简单的getchar();
和{{1} }调用。)当您尝试做得更好时,通常会因缺乏缓冲或系统调用数量的谬误而产生一些错误的假设。
putchar(c);
在标准输出为管道时进行完全缓冲(实际上,除非文件描述符将stdio
赋予{{1},否则它总是进行完全缓冲}函数调用),因此,在您希望尽快看到输出的情况下,应该这样做,至少不要对输出进行缓冲(使用true
或isatty(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用一个字符进行stdio
和while ((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()
(通常)是宏,避免了函数调用的开销)。 [只看一下生成的程序集。