C++ 中优雅地调用脚本

0 前言

当你不得不在 C++ 程序中调用脚本的时候,将就着用 system()?No,我有更好的方法,不仅可以捕获脚本输出,还能实现调用超时。

1 fork + execlp + pipe 优雅搞定脚本调用

  • C++ 中几种创建进程和调用脚本方式:

    1. system() 建立独立进程,拥有独立的代码空间和内存空间。
    2. fork() 创建进程,克隆父进程的代码。
    3. execlp() 代替当前进程的代码空间中的代码数据,函数本身不创建新的进程。
  • system() 无能为力的地方:

    • system() 只能建的的通过执行命令的返回状态来判断脚本的执行情况,如果脚本有复杂的文本返回值则无法处理。
    • system() 无法设置超时时间,不够灵活。
  • 使用 fork + execlp + pipe 虽然复杂一些,但是可以通过进程间的管道来重定向脚本的标准输出,从而拿到脚本的执行日志和返回值,同时可以设置超时时间,比 system() 简单的调用要优雅不少。

  • 小技巧

    • 如果脚本中有后台调用,则后台调用会把 pipe 的输出 hang 住,直到后台调用也执行完成,这种情况往往不是大家想要的。可以在脚本执行结束时打印 ‘\0’,调用程序通过识别 ‘\0’ 来结束调用,而不单单等 pipe 的数据流结束。
    • 在调用 waitpid 等待脚本执行结束时使用 WNOHANG,实现调用超时,但是要使用从超时功能的话,就不能同时使用 pipe 捕获脚本数据了,因为 read 可能会 hang 住。
  • 实例代码

    int pipefd[2];
    ret = pipe(pipefd);

    if((pid = fork()) == 0) {
        // child process
        close(pipefd[0]);  
        dup2(pipefd[1], 1);
        switch(param_count) {
        case 0:
            ret = execlp(cmd, cmd, NULL);
            break;
        case 2:
            ret = execlp(cmd, cmd, param[0], param[1], NULL);
            break;
        default:
            break;
        }
        if (ret) {
            printf("execlp run error %s\n", cmd);
        }
    } else if(pid > 0) {
        // parent process
        int read_size;
        char buffer[32]="";
        char *ptr = res_buf;
        int overflow = 0;
        close(pipefd[1]); 
        int res_len = 0;
        while ((read_size = read(pipefd[0], buffer, sizeof(buffer)-1)) != 0) {
            buffer[read_size] = '\0';
            if (read_size + res_len < res_buf_len) {
                strcpy(ptr + res_len, buffer);
                if (buffer[read_size - 1] == 0) {
                    break;
                }
            } else {
                overflow = 1;
                break;
            }
            res_len += read_size;
        }
        if (overflow) {
            while ((read_size = read(pipefd[0], buffer, sizeof(buffer)-1)) != 0) {}
            log2(g_log, "%s output overflow\n", cmd);
        }
        waitpid(pid, &status, 0); 
        //调用超时的实现逻辑: while(timeout){waitpid(pid, &status, WNOHANG);sleep(1);}
        close(pipefd[0]); 
    } else { 
        close(pipefd[0]); 
        close(pipefd[1]); 
        snprintf(res_buf, res_buf_len, "fork error");        
        log2(g_log, "%s fork error\n", cmd);
        return -1;
    }

你可能感兴趣的:(Linux,c++,c语言)