C - 如何管道只读取文件的程序

时间:2018-02-03 16:29:13

标签: c pipe exec

我想将一个字符串传递给一个程序,该程序只从文件中读取输入,而不是从stdin读取。从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] = '\0';
    }
    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 :更多详情

由于我仍无法使我的程序正常工作,因此我发布了完整的代码

#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://stackoverflow.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] = '\0';

        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] = '\0';
            memset(ch, '\0', 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显示。
使用管道时不起作用。

2 个答案:

答案 0 :(得分:1)

最后的解释

使用PhantomJS进行的一些实验表明,问题是在发送到PhantomJS的JavaScript程序结束时写入一个空字节。 这突出了两个错误:

  1. 问题中的程序会发送一个不必要的空字节。
  2. PhantomJS 2.1.1(在运行macOS High Sierra 10.13.3的Mac上)在其他有效程序后跟空字节时挂起
  3. 问题中的代码包含:

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

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

    将该行更改为:

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

    并且代码按预期工作 - 至少在运行macOS High Sierra 10.13.3的Mac上使用PhantomJS 2.1.1。

    初步分析

    你没有在孩子身上关闭足够的文件描述符。

    • 经验法则:如果你 dup2() 管道的一端到标准输入或标准输出,关闭两者 返回的原始文件描述符 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()是否失败。如果它返回,则失败。如果成功,则不会返回。

    可能还有另一个问题。父进程在从孩子读取任何内容之前等待孩子退出。但是,如果孩子尝试写入的数据多于管道中的数据,则进程将挂起,等待某个进程(必须是父进程)才能读取管道。既然他们会在他们做对方等待的事情之前等待对方做某事,那就是(或者至少可能是)僵局。

    您还应该修改父进程,以便在等待之前进行阅读。

    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] = '\0';
        }
        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字节或更多)。只是不要复制比读取的数据更多的数据,这就是全部。反复使用realloc()向缓冲区分配一个字节最终会非常缓慢。如果只有几个字节的数据,那就不重要了;如果要处理千字节或更多数据,那将很重要。

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

    仍有问题2018-02-03

    我将修改后的代码解压缩到一个程序(pipe89.c,编译为pipe89)。当分配的空间发生变化时,我遇到了不一致的崩溃。我终于意识到你要重新分配一个字节太少的空间 - 它花费的时间比它应该做的要长很多(但如果Valgrind可用于macOS High Sierra,它会有所帮助 - 它还没有)。 / p>

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

    #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] = '\0';
    
            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] = '\0';
            }
            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,并分配额外的空间一下子。对于内存分配系统来说,这是一项不那么密集的锻炼。

    2018-02-05继续存在的问题

    从编辑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脚本。)

    当我使用此{&#39; phantomjs&#39;运行您的程序时,我得到了我期待的输出:

    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://stackoverflow.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来打印数据,所以它不会注意到发送给孩子的无关空字节。

答案 1 :(得分:1)

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

  

如果进程尝试写入          完整管道(见下文),然后写入(2)块,直到有足够的数据          已从管道中读取以允许写入完成。非阻塞          通过使用fcntl(2)F_SETFL操作来启用I / O.          O_NONBLOCK打开文件状态标志。

  

管道容量有限。如果管道已满,则写入(2)          将阻塞或失败,具体取决于是否设置了O_NONBLOCK标志          (见下文)。不同的实现具有不同的限制          管道容量。应用程序不应该依赖于特定的          容量:应该设计一个应用程序,以便进行阅读过程          一旦可用就会消耗数据,以便进行写入过程          不会被阻止。

在您编写的代码中,等待,然后再阅读

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正在等待数据读取,您应该{<1}}