C - 如何通过管道传输到只读文件的程序

C - How to pipe to a program that read only from file

我想将一个字符串通过管道传输到一个程序,该程序只从文件中读取输入,而不是从标准输入中读取输入。从 bash 使用它,我可以做类似

的事情
echo "hi" | program /dev/stdin

我想从 C 代码中复制这种行为。我做的是这个

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>

int main() {
 pid_t pid;
 int rv;

 int to_ext_program_pipe[2];
 int to_my_program_pipe[2];

 if(pipe(to_ext_program_pipe)) {
    fprintf(stderr,"Pipe error!\n");
    exit(1);
 }
 if(pipe(to_my_program_pipe)) {
    fprintf(stderr,"Pipe error!\n");
    exit(1);
 }

 if( (pid=fork()) == -1) {
    fprintf(stderr,"Fork error. Exiting.\n");
    exit(1);
 }

 if(pid) {
    close(to_my_program_pipe[1]);
    close(to_ext_program_pipe[0]);
    char string_to_write[] = "this is the string to write";

    write(to_ext_program_pipe[1], string_to_write, strlen(string_to_write) + 1);
    close(to_ext_program_pipe[1]);

    wait(&rv);
    if(rv != 0) {
        fprintf(stderr, "%s %d\n", "phantomjs exit status ", rv);
        exit(1);
    }

    char *string_to_read;
    char ch[1];
    size_t len = 0;
    string_to_read = malloc(sizeof(char));
    if(!string_to_read) {

        fprintf(stderr, "%s\n", "Error while allocating memory");

        exit(1);
    }
    while(read(to_my_program_pipe[0], ch, 1) == 1) {
        string_to_read[len]=ch[0];
        len++;
        string_to_read = realloc(string_to_read, len*sizeof(char));
        if(!string_to_read) {
            fprintf(stderr, "%s\n", "Error while allocating memory");
        }
        string_to_read[len] = '[=11=]';
    }
    close(to_my_program_pipe[0]);
    printf("Output: %s\n", string_to_read);
    free(string_to_read);
} else {
    close(to_ext_program_pipe[1]);
    close(to_my_program_pipe[0]);

    dup2(to_ext_program_pipe[0],0);
    dup2(to_my_program_pipe[1],1);

    if(execlp("ext_program", "ext_program", "/dev/stdin" , NULL) == -1) {
        fprintf(stderr,"execlp Error!");
        exit(1);
    }
    close(to_ext_program_pipe[0]);
    close(to_my_program_pipe[1]);
}

 return 0; 
}

它不工作。

编辑 我没有得到 ext_program 输出,应该保存在 string_to_read 中。该程序只是挂起。我可以看到 ext_program 被执行了,但是我没有得到任何东西

我想知道是否有错误,或者我想要的东西无法完成。我也知道另一种方法是使用命名管道。


编辑 2:更多细节

由于我的程序仍然无法运行,我post完整的代码

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>

int main() {

    pid_t pid;
    int rv;
    int to_phantomjs_pipe[2];
    int to_my_program_pipe[2];

    if(pipe(to_phantomjs_pipe)) {
        fprintf(stderr,"Pipe error!\n");
        exit(1);
    }
    if(pipe(to_my_program_pipe)) {
        fprintf(stderr,"Pipe error!\n");
        exit(1);
    }

    if( (pid=fork()) == -1) {
        fprintf(stderr,"Fork error. Exiting.\n");
        exit(1);
    }

    if(pid) {
        close(to_my_program_pipe[1]);
        close(to_phantomjs_pipe[0]);

        char jsToExectue[] = "var page=require(\'webpage\').create();page.onInitialized=function(){page.evaluate(function(){delete window._phantom;delete window.callPhantom;});};page.onResourceRequested=function(requestData,request){if((/http:\/\/.+\?\\.css/gi).test(requestData[\'url\'])||requestData.headers[\'Content-Type\']==\'text/css\'){request.abort();}};page.settings.loadImage=false;page.settings.userAgent=\'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36\';page.open(\'https://whosebug.com\',function(status){if(status!==\'success\'){phantom.exit(1);}else{console.log(page.content);phantom.exit();}});";

        write(to_phantomjs_pipe[1], jsToExectue, strlen(jsToExectue) + 1);
        close(to_phantomjs_pipe[1]);

        int read_chars;
        int BUFF=1024;
        char *str;
        char ch[BUFF];
        size_t len = 0;
        str = malloc(sizeof(char));
        if(!str) {
            fprintf(stderr, "%s\n", "Error while allocating memory");
            exit(1);
        }
        str[0] = '[=12=]';

        while( (read_chars = read(to_my_program_pipe[0], ch, BUFF)) > 0)
        {
            len += read_chars;
            str = realloc(str, (len + 1)*sizeof(char));
            if(!str) {
                fprintf(stderr, "%s\n", "Error while allocating memory");
            }
            strcat(str, ch);
            str[len] = '[=12=]';
            memset(ch, '[=12=]', BUFF*sizeof(ch[0]));
        }
        close(to_my_program_pipe[0]);
        printf("%s\n", str);
        free(str);

        wait(&rv);
        if(rv != 0) {
            fprintf(stderr, "%s %d\n", "phantomjs exit status ", rv);
            exit(1);
        }
    } else {
        dup2(to_phantomjs_pipe[0],0);
        dup2(to_my_program_pipe[1],1);

        close(to_phantomjs_pipe[1]);
        close(to_my_program_pipe[0]);
        close(to_phantomjs_pipe[0]);
        close(to_my_program_pipe[1]);

        execlp("phantomjs", "phantomjs", "--ssl-protocol=TLSv1", "/dev/stdin" , (char *)NULL);
    }

    return 0;
}

我想做的是将脚本传递给 phantomjs 以通过管道执行,然后将结果 HTML 作为字符串读取。我按照说明修改了代码,但是 phantomjs 仍然没有从 stdin 读取。
我通过创建一个将脚本字符串写入文件然后正常执行 phantomjs 的愚蠢程序来测试脚本字符串并且有效。
我还尝试执行
execlp("phantomjs", "phantomjs", "--ssl-protocol=TLSv1", "path_to_script_file" , (char *)NULL);
并且它也有效,显示了输出 HTML 。
使用管道时无效。

终于有了解释

PhantomJS 的一些实验表明问题是在发送到 PhantomJS 的 JavaScript 程序末尾写了一个空字节。 这突出了两个错误:

  1. 问题中的程序发送了一个不必要的空字节。
  2. PhantomJS 2.1.1(在 Mac 运行ning macOS High Sierra 10.13.3 上)在其他有效程序后跟空字节时挂起[=13​​1=]

问题中的代码包含:

write(to_phantomjs_pipe[1], jsToExectue, strlen(jsToExectue) + 1);

+ 1表示终止字符串的空字节也写入phantomjs。写入该空字节会导致 phantomjs 挂起。这无异于一个错误——当然不清楚为什么 PhantomJS 在没有检测到 EOF(没有更多数据要来)的情况下挂起,也没有给出错误等。

将该行更改为:

write(to_phantomjs_pipe[1], jsToExectue, strlen(jsToExectue));

并且代码按预期工作——至少在 Mac 运行ning macOS High Sierra 10.13.3.

上使用 PhantomJS 2.1.1

初步分析

您没有关闭子项中足够的文件描述符。

  • 经验法则:如果你 dup2() 管道的一端连接到标准输入或标准输出,同时关闭 原始文件描述符 return编辑者 pipe() 尽早。 特别是,您应该在使用任何 exec*() 函数族。

    如果您使用以下任一方式复制描述符,则该规则也适用 dup() 要么 fcntl() F_DUPFD

显示的子代码是:

} else {
    close(to_ext_program_pipe[1]);
    close(to_my_program_pipe[0]);

    dup2(to_ext_program_pipe[0],0);
    dup2(to_my_program_pipe[1],1);

    if(execlp("ext_program", "ext_program", "/dev/stdin" , NULL) == -1) {
        fprintf(stderr,"execlp Error!");
        exit(1);
    }
    close(to_ext_program_pipe[0]);
    close(to_my_program_pipe[1]);
}

最后两个 close() 语句永远不会执行;他们需要出现在 execlp().

之前

您需要的是:

} else {
    dup2(to_ext_program_pipe[0], 0);
    dup2(to_my_program_pipe[1], 1);
    close(to_ext_program_pipe[0]);
    close(to_ext_program_pipe[1]);
    close(to_my_program_pipe[0]);
    close(to_my_program_pipe[1]);

    execlp("ext_program", "ext_program", "/dev/stdin" , NULL);
    fprintf(stderr, "execlp Error!\n");
    exit(1);
}

您可以重新排序它拆分 close() 个调用,但最好将它们重新组合,如图所示。

注意不需要测试execlp()是否失败。如果是 returns,则失败。如果成功,则不会 return.


可能还有其他问题。父进程等待子进程退出,然后再从子进程读取任何内容。然而,如果子进程试图写入比管道容纳的更多的数据,进程将挂起,等待某个进程(必须是父进程)读取管道。由于他们都在等待对方做某事,然后他们才会做对方正在等待的事情,这是(或者,至少,可能是)一个僵局。

你也应该修改父进程做等待前的读取。

if (pid) {
    close(to_my_program_pipe[1]);
    close(to_ext_program_pipe[0]);
    char string_to_write[] = "this is the string to write";

    write(to_ext_program_pipe[1], string_to_write, strlen(string_to_write) + 1);
    close(to_ext_program_pipe[1]);

    char *string_to_read;
    char ch[1];
    size_t len = 0;
    string_to_read = malloc(sizeof(char));
    if(!string_to_read) {
        fprintf(stderr, "%s\n", "Error while allocating memory");
        exit(1);
    }
    while (read(to_my_program_pipe[0], ch, 1) == 1) {
        string_to_read[len] = ch[0];
        len++;
        string_to_read = realloc(string_to_read, len*sizeof(char));
        if (!string_to_read) {
            fprintf(stderr, "%s\n", "Error while allocating memory\n");
            exit(1);
        }
        string_to_read[len] = '[=14=]';
    }
    close(to_my_program_pipe[0]);
    printf("Output: %s\n", string_to_read);
    free(string_to_read);

    wait(&rv);
    if (rv != 0) {
        fprintf(stderr, "%s %d\n", "phantomjs exit status ", rv);
        exit(1);
    }
} …

我还会重写代码以大块读取(1024 字节或更多)。只是不要复制比读取的 return 多的数据,仅此而已。重复使用 realloc() 为缓冲区再分配一个字节最终会非常慢。如果只有几个字节的数据,那无关紧要;如果有千字节或更多数据要处理,这将很重要。

后来: 由于 PhantomJS 程序生成了超过 90 KiB 的数据以响应它发送的消息,这是问题的一个因素——或者如果它是不适用于 PhantomJS 中的 hang-on-null-byte 错误。

仍有问题 2018-02-03

我将修改后的代码提取到一个程序中(pipe89.c,编译为 pipe89)。当 space 分配更改时,我遇到了不一致的崩溃。我最终意识到你重新分配的一个字节太少 space — 它花费的时间比应该完成的时间长得多(但如果 Valgrind 可用于 macOS High Sierra 会有所帮助 — 目前还没有)。

这是带有调试信息注释输出的固定代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

int main(void)
{
    pid_t pid;
    int rv;

    int to_ext_program_pipe[2];
    int to_my_program_pipe[2];

    if (pipe(to_ext_program_pipe))
    {
        fprintf(stderr, "Pipe error!\n");
        exit(1);
    }
    if (pipe(to_my_program_pipe))
    {
        fprintf(stderr, "Pipe error!\n");
        exit(1);
    }

    if ((pid = fork()) == -1)
    {
        fprintf(stderr, "Fork error. Exiting.\n");
        exit(1);
    }

    if (pid)
    {
        close(to_my_program_pipe[1]);
        close(to_ext_program_pipe[0]);
        char string_to_write[] = "this is the string to write";

        write(to_ext_program_pipe[1], string_to_write, sizeof(string_to_write) - 1);
        close(to_ext_program_pipe[1]);

        char ch[1];
        size_t len = 0;
        char *string_to_read = malloc(sizeof(char));
        if (string_to_read == 0)
        {
            fprintf(stderr, "%s\n", "Error while allocating memory");
            exit(1);
        }
        string_to_read[len] = '[=15=]';

        while (read(to_my_program_pipe[0], ch, 1) == 1)
        {
            //fprintf(stderr, "%3zu: got %3d [%c]\n", len, ch[0], ch[0]); fflush(stderr);
            string_to_read[len++] = ch[0];
            char *new_space = realloc(string_to_read, len + 1);     // KEY CHANGE is " + 1"
            //if (new_space != string_to_read)
            //    fprintf(stderr, "Move: len %zu old %p vs new %p\n", len, (void *)string_to_read, (void *)new_space);
            if (new_space == 0)
            {
                fprintf(stderr, "Error while allocating %zu bytes memory\n", len);
                exit(1);
            }
            string_to_read = new_space;
            string_to_read[len] = '[=15=]';
        }
        close(to_my_program_pipe[0]);
        printf("Output: %zu (%zu) [%s]\n", len, strlen(string_to_read), string_to_read);
        free(string_to_read);

        wait(&rv);
        if (rv != 0)
        {
            fprintf(stderr, "%s %d\n", "phantomjs exit status ", rv);
            exit(1);
        }
    }
    else
    {
        dup2(to_ext_program_pipe[0], 0);
        dup2(to_my_program_pipe[1], 1);
        close(to_ext_program_pipe[0]);
        close(to_ext_program_pipe[1]);
        close(to_my_program_pipe[0]);
        close(to_my_program_pipe[1]);

        execlp("ext_program", "ext_program", "/dev/stdin", NULL);
        fprintf(stderr, "execlp Error!\n");
        exit(1);
    }

    return 0;
}

它是在一个程序上测试的,该程序为 27 个字节的输入写出 5590 个字节。这不像您的程序中那样大,但它证明了一点。

我仍然认为你最好不要一次重新分配一个额外的字节——扫描循环应该使用一个缓冲区,比如 1 KiB,一次最多读取 1 KiB,然后分配额外的字节space 一下子。这对内存分配系统来说强度要小得多。

问题在 2018-02-05 继续

从 Edit 2 中获取代码并仅将函数定义从 int main() { 更改为 int main(void) {(因为我使用的编译选项不允许旧式非原型函数声明或定义,没有 void,那不是原型),代码是 对我来说工作正常。我创建了一个代理 phantomjs 程序(来自另一个我已经在使用的程序),如下所示:

#include <stdio.h>

int main(int argc, char **argv, char **envp)
{
    for (int i = 0; i < argc; i++)
        printf("argv[%d] = <<%s>>\n", i, argv[i]);
    for (int i = 0; envp[i] != 0; i++)
        printf("envp[%d] = <<%s>>\n", i, envp[i]);
    FILE *fp = fopen(argv[argc - 1], "r");
    if (fp != 0)
    {
        int c;
        while ((c = getc(fp)) != EOF)
            putchar(c);
        fclose(fp);
    }
    else
        fprintf(stderr, "%s: failed to open file %s for reading\n",
                argv[0], argv[argc-1]);
    return(0);
}

此代码回显参数列表、环境,然后打开名为最后一个参数的文件并将其复制到标准输出。 (由于对 argv[argc-1] 的特殊处理,它是高度专业化的,但之前的代码偶尔对调试复杂的 shell 脚本很有用。)

当我 运行 你的程序使用这个 'phantomjs' 时,我得到了我期望的输出:

argv[0] = <<phantomjs>>
argv[1] = <<--ssl-protocol=TLSv1>>
argv[2] = <</dev/stdin>>
envp[0] = <<MANPATH=/Users/jleffler/man:/Users/jleffler/share/man:/Users/jleffler/oss/share/man:/Users/jleffler/oss/rcs/man:/usr/local/mysql/man:/opt/gcc/v7.3.0/share/man:/Users/jleffler/perl/v5.24.0/man:/usr/local/man:/usr/local/share/man:/usr/share/man:/opt/gnu/share/man>>
envp[1] = <<IXH=/opt/informix/12.10.FC6/etc/sqlhosts>>
…
envp[49] = <<HISTFILE=/Users/jleffler/.bash.jleffler>>
envp[50] = <<_=./pipe31>>
var page=require('webpage').create();page.onInitialized=function(){page.evaluate(function(){delete window._phantom;delete window.callPhantom;});};page.onResourceRequested=function(requestData,request){if((/http:\/\/.+?\.css/gi).test(requestData['url'])||requestData.headers['Content-Type']=='text/css'){request.abort();}};page.settings.loadImage=false;page.settings.userAgent='Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36';page.open('https://whosebug.com',function(status){if(status!=='success'){phantom.exit(1);}else{console.log(page.content);phantom.exit();}});

在这一点上,我不得不把矛头指向phantomjs在你的环境中;当您执行相当于:

时,它的行为似乎并不像预期的那样
echo "$JS_PROG" | phantomjs /dev/stdin | cat

当然,我无法再重现您的问题。

  • 你应该使用我的代理 phantomjs 代码并使用它来代替真正的 phantomjs 看看你得到了什么。

    • 如果您得到类似于我所展示的输出,那么问题出在真正的 phantomjs
    • 如果您没有得到与我展示的类似的输出,那么您的代码从更新到问题可能存在问题。

之后: 请注意,因为 printf() 使用 %s 来打印数据,所以它不会注意到发送给子进程的无关空字节.

pipe(7) man 中写到你应该尽快从管道读取:

If a process attempts to write to a full pipe (see below), then write(2) blocks until sufficient data has been read from the pipe to allow the write to complete. Nonblocking I/O is possible by using the fcntl(2) F_SETFL operation to enable the O_NONBLOCK open file status flag.

A pipe has a limited capacity. If the pipe is full, then a write(2) will block or fail, depending on whether the O_NONBLOCK flag is set (see below). Different implementations have different limits for the pipe capacity. Applications should not rely on a particular capacity: an application should be designed so that a reading process consumes data as soon as it is available, so that a writing process does not remain blocked.

在您编写的代码中,等待然后才读取

write(to_ext_program_pipe[1], string_to_write, strlen(string_to_write) + 1);
close(to_ext_program_pipe[1]);

wait(&rv);
//...
while(read(to_my_program_pipe[0], ch, 1) == 1) {
//...

也许管道已满或ext_program正在等待读取数据,您应该wait()读取之后。