如何正确地 fread & fwrite from & to a pipe
How to properly fread & fwrite from & to a pipe
我有这段代码充当两个 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 调用没有' t完成,所以以后可能不会为空),它会关闭管道并结束。
第三个实现是我想做的,它相对有效(所有文本都由 less
接收),但由于某种原因它卡住了并且没有关闭管道(似乎就像它永远不会得到 EOF)。
编辑: 第三个实现有问题。第四次尝试解决错误,但现在 less
没有收到任何东西。
这应该如何正确完成?
最简单的解决方案:
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()
将(通常)是一个宏,避免函数调用开销)。 [只看生成的程序集。
首先,我认为您在缓冲方面遇到的问题比效率方面的问题更多。这是第一次处理 stdio
包时的常见问题。
其次,从输入到输出的简单数据复制器的最佳(也是最简单)实现是以下代码段(从 K&R 第一版复制。)。
while((c = fgetc(input)) != EOF)
fputc(c, output);
(好吧,不是文字副本,因为在那里,K&R 使用 stdin
和 stdout
作为 FILE*
描述符,并且他们使用更简单的 getchar();
和 putchar(c);
调用。)当你试图做得比这更好时,通常你会产生一些错误的假设,如缺乏缓冲或系统调用次数的谬误。
stdio
当标准输出是一个管道 时进行完全缓冲(事实上,它总是进行完全缓冲,除非文件描述符给出 true
到isatty(3)
函数调用),所以你应该这样做,如果你想尽快看到输出,至少,没有输出缓冲(像 setbuf(out, NULL);
或 fflush()
) 你的输出在某个时候,所以当你在输入中等待更多数据时它不会在输出中缓冲。
您似乎看到 less(1)
程序的输出不可见,因为它在您的程序内部进行了缓冲。而这正是正在发生的事情......假设你提供你的程序(尽管处理了单个字符,它正在做完整的缓冲)在完整的输入缓冲区(BUFSIZ
字符)之前没有得到任何输入已经喂给它了。然后,很多单个 fgetc()
调用在一个循环中完成,很多 fputc()
调用在一个循环中完成(每个 BUFSIZ
调用)并且缓冲区在输出。但是这个缓冲区没有被写入,因为它还需要一个字符来强制刷新。因此,在您获得前两个 BUFSIZ
数据块之前,您不会将任何内容写入 less(1)
.
一个简单有效的方法是在 fputc(c, out);
之后检查字符是否为 \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);
为了获得最佳性能,同时不会不必要地阻塞缓冲区中的输出数据。
顺便说一句,对一个字符执行 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)
程序,但功能有所减少)
我有这段代码充当两个 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 调用没有' t完成,所以以后可能不会为空),它会关闭管道并结束。
第三个实现是我想做的,它相对有效(所有文本都由 less
接收),但由于某种原因它卡住了并且没有关闭管道(似乎就像它永远不会得到 EOF)。
编辑: 第三个实现有问题。第四次尝试解决错误,但现在 less
没有收到任何东西。
这应该如何正确完成?
最简单的解决方案:
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()
将(通常)是一个宏,避免函数调用开销)。 [只看生成的程序集。
首先,我认为您在缓冲方面遇到的问题比效率方面的问题更多。这是第一次处理 stdio
包时的常见问题。
其次,从输入到输出的简单数据复制器的最佳(也是最简单)实现是以下代码段(从 K&R 第一版复制。)。
while((c = fgetc(input)) != EOF)
fputc(c, output);
(好吧,不是文字副本,因为在那里,K&R 使用 stdin
和 stdout
作为 FILE*
描述符,并且他们使用更简单的 getchar();
和 putchar(c);
调用。)当你试图做得比这更好时,通常你会产生一些错误的假设,如缺乏缓冲或系统调用次数的谬误。
stdio
当标准输出是一个管道 时进行完全缓冲(事实上,它总是进行完全缓冲,除非文件描述符给出 true
到isatty(3)
函数调用),所以你应该这样做,如果你想尽快看到输出,至少,没有输出缓冲(像 setbuf(out, NULL);
或 fflush()
) 你的输出在某个时候,所以当你在输入中等待更多数据时它不会在输出中缓冲。
您似乎看到 less(1)
程序的输出不可见,因为它在您的程序内部进行了缓冲。而这正是正在发生的事情......假设你提供你的程序(尽管处理了单个字符,它正在做完整的缓冲)在完整的输入缓冲区(BUFSIZ
字符)之前没有得到任何输入已经喂给它了。然后,很多单个 fgetc()
调用在一个循环中完成,很多 fputc()
调用在一个循环中完成(每个 BUFSIZ
调用)并且缓冲区在输出。但是这个缓冲区没有被写入,因为它还需要一个字符来强制刷新。因此,在您获得前两个 BUFSIZ
数据块之前,您不会将任何内容写入 less(1)
.
一个简单有效的方法是在 fputc(c, out);
之后检查字符是否为 \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);
为了获得最佳性能,同时不会不必要地阻塞缓冲区中的输出数据。
顺便说一句,对一个字符执行 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)
程序,但功能有所减少)