在《system() 执行 shell 命令》中,我们介绍了 system 执行 shell 命令的方法,system 返回值比较混乱,难以理解,而且 popen 在处理子进程标准输出上会很方便。

注意:管道只能处理标准输出,不能处理标准错误输出。


popen 和 pclose 的实现与 system 类似,多了一步创建管道的操作。

popen 成功返回 FILE 句柄,失败返回 NULL, 失败的原因可能为 fork 或 pipe 失败,也可能为分配内存失败;

pclose 失败返回 -1, 成功则返回 exit status, 同 system 类似,需要用 WIFEXITED, WEXITSTATUS 等获取命令返回值。


此外,同 system 类似, pclose 会等待进程退出,如果等不到进程退出(例如已经被 wait 回收),则 error 设置为 ECHILD.

注意:

1. pclose 仅仅是为了回收子进程,避免僵尸进程的产生;

2. 和 system 一样,SIGCHLD  依然会影响 popen,见示例程序。

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

typedef void (*sighandler_t)(int);

int main(int argc, char* argv[])
{
    char cmd[1024];
    char line[1024];
    FILE* pipe;
    int rv;

    if (argc != 2)
    {
        printf("Usage: %s <path>\n", argv[0]);
        return -1;
    }

    // pclose fail: No child processes
    signal(SIGCHLD, SIG_IGN);
    
    
    snprintf(cmd, sizeof(cmd), "ls -l %s 2>/dev/null", argv[1]);

    //sighandler_t old_sighandler = signal(SIGCHLD, SIG_DFL);
    pipe = popen(cmd, "r");
    if(NULL == pipe)
    {
        printf("popen() failed: %s\n", cmd);
        return -1;
    }
    
    while(fgets(line, sizeof(line),pipe) != NULL)
    {
        printf("%s", line);
    }


    rv = pclose(pipe);
    //signal(SIGCHLD, old_sighandler);
    
    if (-1 == rv)
    {
        printf("pclose() failed: %s\n", strerror(errno));
        return -1;
    }
    
    if (WIFEXITED(rv))
    {
        printf("subprocess exited, exit code: %d\n", WEXITSTATUS(rv));
        if (0 == WEXITSTATUS(rv))
        {
            // if command returning 0 means succeed
            printf("command succeed\n");
        }
        else
        {
            if(127 == WEXITSTATUS(rv))
            {
                printf("command not found\n");
                return WEXITSTATUS(rv);
            }
            else
            {
                printf("command failed: %s\n", strerror(WEXITSTATUS(rv)));
                return WEXITSTATUS(rv);
            }
        }
    }
    else
    {
        printf("subprocess exit failed\n");
        return -1;
    }
 
    return 0;
}



说明:

1. 将 SIGCHLD 忽略之后,pclose 不能回收子进程退出信息,errno 为 ECHILD, 错误信息为 "No child processes";

2. 由于管道不能捕获 stderr, 因此命令将 stderr 重定向至 /dev/null.