从C程序中检测关闭的unix管道

时间:2017-04-22 19:04:20

标签: c pipe

我正在编写一个程序(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是否已关闭。它正在移除缓冲区,因此管道按预期运行。

2 个答案:

答案 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.hstderr.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.