我有这段代码,它充当两个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()
实现它。我试了三种方法。第一个是用
fgetc()
&fputc()
实现的,所以速度会很慢。但是它工作正常,因为它检查EOF
,所以它将等待cat
(或我使用的任何shell调用)完成它的工作。第二个比较快,但是我担心我没有检查
EOF
所以如果有任何时刻管道是空的(但是shell调用还没有完成,所以将来可能不会是空的),它将关闭管道并结束。第三个实现是我想做的,它相对有效(所有文本都由
less
接收),但由于某种原因,它卡住了,没有关闭管道(似乎它永远不会得到EOF)。编辑:第三个实现是错误的。第四个尝试解决这个bug,但是现在
less
没有收到任何消息。这应该怎么做?
最佳答案
首先,我认为你在缓冲方面的问题比效率方面的问题要多。这是第一次处理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)
程序的输出不可见,因为它被缓冲在程序的内部。而这正是正在发生的。。。假设您的程序feed(尽管对单个字符进行了处理,但它正在执行完全缓冲)在完全输入缓冲区(BUFSIZ
个字符)被提供给它之前不会得到任何输入。然后,许多单个fgetc()
调用在一个循环中完成,许多fputc()
调用在一个循环中完成(每个调用都是BUFSIZ
调用),并且在输出端填充缓冲区。但是这个缓冲区没有被写入,因为它需要一个字符来强制刷新。因此,在获得前两个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);
为了获得最佳性能,同时不必要地阻塞缓冲区中的输出数据。
顺便说一句,对一个字符进行
fread()
和fwrite()
操作是浪费时间的,也是使事情复杂化的一种方式(而且容易出错)。一个字符的fwrite()
不会避免使用缓冲区,因此您不会获得比使用fputc(c, output);
更高的性能。顺便说一句(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)
程序,但功能有所减少)
关于c - 如何从管道正确读取和写入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55668731/