我正在编写一个程序(A),其输出意味着通过管道输送到另一个程序(B)。如果B没有读取所有A的数据,我希望A处理错误。
为简单起见,我已从以下示例中删除了不需要的代码。
int main(int argc, char *argv[])
{
signal(SIGPIPE, SIG_IGN);
FILE *file = fopen("test.txt" , "r");
if (!file)
... Error handling ...
int next;
while ((next = fgetc(file)) != EOF)
if (fputc(next, stdout) < 0 || fflush(stdout) != 0)
... print error message to stderr ...
}
这个程序编译得很好但是它间歇性地工作。我最初认为这是因为stdout中的某种缓冲,这就是为什么我添加了同花顺。
我知道这可能不是最好的方法,但这只是一个侧面项目。有没有办法关闭缓冲?我应该使用其他一些转移方法吗?在我四处寻找解决方案时,我看到有人使用mkfifo。
编辑:我的问题是不检查stdout是否已关闭。它正在移除缓冲区,因此管道按预期运行。
答案 0 :(得分:1)
以下是您的代码的两种变体,转换为完整的工作程序。简陋的版本是cp41.c
:
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static inline void putch(char c)
{
if (write(STDOUT_FILENO, &c, sizeof(c)) != sizeof(c))
{
int errnum = errno;
fprintf(stderr, "Attempt to write '%c' failed (%d: %s)\n", c, errnum, strerror(errnum));
exit(EXIT_FAILURE);
}
}
int main(void)
{
signal(SIGPIPE, SIG_IGN);
int c;
while ((c = getchar()) != EOF)
{
putch(c);
}
return 0;
}
更华丽的版本是cp43.c
。您可以在https://github.com/jleffler/soq/tree/master/src/libsoq下的GitHub上找到stderr.h
和stderr.c
的代码。我使用它是因为它使报告错误更容易(单行函数调用而不是多行)。我可以在err_syserr()
函数中使用putch()
但不要在简单版和华丽版之间保持一致。
#include "stderr.h"
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static inline void putch(char c)
{
if (write(STDOUT_FILENO, &c, sizeof(c)) != sizeof(c))
{
int errnum = errno;
fprintf(stderr, "Attempt to write '%c' failed (%d: %s)\n", c, errnum, strerror(errnum));
exit(EXIT_FAILURE);
}
}
static const char usestr[] = "[-l logfile] [-e]";
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
signal(SIGPIPE, SIG_IGN);
const char *filename = NULL;
int to_stderr = 0;
int opt;
while ((opt = getopt(argc, argv, "l:e")) != -1)
{
switch (opt)
{
case 'l':
filename = optarg;
break;
case 'e':
to_stderr = 1;
break;
default:
err_usage(usestr);
/*NOTREACHED*/
}
}
if (optind != argc)
err_usage(usestr);
FILE *fp = NULL;
if (filename != NULL)
fp = fopen(filename, "w");
int c;
while ((c = getchar()) != EOF)
{
if (fp)
putc(c, fp);
if (to_stderr)
putc(c, stderr);
putch(c);
}
return 0;
}
这允许用户将正在处理的内容从标准输入复制到标准错误和/或日志文件。结果证明这很有用。
准系统代码变得“不可靠”。例如:
$ cp41 < cp41.c | sed 1q
#include <errno.h>
$
然而,给它足够的输入并且变得可靠:
$ cp41 < <(cat cp41.c cp41.c cp41.c) | sed 1q
#include <errno.h>
Attempt to write 'n' failed (32: Broken pipe)
$
使用cp43
:
$ ./cp43 <cp43.c | sed 3q
#include "stderr.h"
#include <errno.h>
#include <signal.h>
Attempt to write 'e' failed (32: Broken pipe)
$
使用-e
选项:
$ ./cp43 -e <cp43.c | sed 3q
#include "stderr.h"
#include <errno.h>
#include <signal.h>
#in#include "stderr.h"
cl#include <errno.h>
u#include <signal.h>
de <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static inline void putch(char c)
{
if (write(STDOUT_FILENO, &c, sizeof(c)) != sizeof(c))
{
int erAttempt to write 'r' failed (32: Broken pipe)
$
或使用-l cp43.log
选项:
$ ./cp43 -l cp43.log <cp43.c | sed 3q
#include "stderr.h"
#include <errno.h>
#include <signal.h>
Attempt to write ' ' failed (32: Broken pipe)
$ cat cp43.log
#include "stderr.h"
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static inline void putch(char c)
{
if (write(STDOUT_FILENO, &c, sizeof(c)) != sizeof(c))
{
int errnum = errno;
fprintf(stderr, "Attempt to write '%c' failed (%d: %s)\n", c, errnum, strerror(errnum));
exit(EXIT_FAILURE);
}
}
static const char usestr[] = "[-l logfile] [-e]";
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
signal(SIGPIPE, SIG_IGN);
const char *filename = NULL;
int to_stderr = $
注意在sed 3q
命令读取之前已将多少材料写入日志。最后的$
提示位于日志文件的部分行的末尾。
麻烦的是,如果写入程序在sed 3q
之前已经足够远,它可能在sed
终止之前完成读取和写入,因此它永远不会获得SIGPIPE信号,因为它写道,管道永远不会“没有读者”。
您可以使用其他程序代替sed
来解决问题。例如:
$ cp41 < cp41.c | echo
Attempt to write '#' failed (32: Broken pipe)
$
答案 1 :(得分:0)
fgetc()
和fputc()
被缓冲,使用read()
和write()
系统调用,以获得无缓冲的I / O.