在《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.