Linux 可以通过三种函数执行 shell 命令,分别是 exec, system, popen。
它们的区别总结如下:
exec # 在当前进程中执行命令,其后所有的代码将被清空,不能执行
system = fork + exec # 在子进程中执行指令
popen = fork + exec + pipe # 重定向子进程的标准输入或输出,提供控制子进程输入或输出的能力
在满足要求的情况下,尽量使用封装的更多的函数。
exec 代表一个函数族,有 7 个相关函数,其作用是替换当前执行的程序为新程序(replaces the current process image with a new process image.)。并不创建新进程,所以进程 ID 未变。exec 执行过后,进程的程序段,数据段,堆段和栈段等全部替换为新的程序,原程序exec之后的代码就不再运行。
#include
int execl(const char *pathname, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execle(const char *pathname, const char *arg, ...
/*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
字母 l 和 字母 v 互斥:
list
of arguments)execl ("/bin/sh", "sh", "-c", command, (char *) 0);
vector
)char *argv[] = {"sh", "-c", command, (char *) 0};
execv("/bin/sh", argv);
字母 p 代表通过 PATH 环境变量来查找可执行文件,因此只用提供文件名。
execlp ("sh", "sh", "-c", command, (char *) 0);
字母 e 代表使用指定的环境变量,函数取一个 envp[] 数组。
char *env_init[] = { "USER=unknown", NULL };
execle("/bin/sh", "sh", "-c", command, (char *) 0, env_init);
#include
#include
#include
#include
#include
#include
void err_sys(char *msg)
{
perror(msg);
exit(1);
}
int main(void)
{
pid_t pid;
char *command = "ls";
char *argv[] = {"sh", "-c", command, (char *) 0};
char *env_init[] = { "USER=unknown", NULL };
printf("execl\n");
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) {
/* execl */
if (execl("/bin/sh", "sh", "-c", command, (char *) 0) < 0) {
err_sys("execl error");
}
}
if (waitpid(pid, NULL, 0) < 0) {
err_sys("wait error");
}
printf("execv\n");
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) {
/* execv */
if (execv("/bin/sh", argv) < 0) {
err_sys("execv error");
}
}
if (waitpid(pid, NULL, 0) < 0) {
err_sys("wait error");
}
printf("execlp\n");
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) {
/* execlp */
if (execlp("sh", "sh", "-c", command, (char *) 0) < 0) {
err_sys("execlp error");
}
}
if (waitpid(pid, NULL, 0) < 0) {
err_sys("wait error");
}
printf("execle\n");
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) {
/* execle */
if (execle("/bin/sh", "sh", "-c", command, (char *) 0, env_init) < 0) {
err_sys("execle error");
}
}
if (waitpid(pid, NULL, 0) < 0) {
err_sys("wait error");
}
return 0;
}
通过先 fork,然后再调用 exec,可以在子进程中执行新的程序。
#include
int system(const char *cmdstring);
以下是 《UNIX环境高级编程》中 system 的简版实现,忽略了信号处理。
#include
#include
#include
int
system(const char *cmdstring) /* version without signal handling */
{
pid_t pid;
int status;
if (cmdstring == NULL)
return(1); /* always a command processor with UNIX */
if ((pid = fork()) < 0) {
status = -1; /* probably out of processes */
} else if (pid == 0) { /* child */
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127); /* execl error */
} else { /* parent */
while (waitpid(pid, &status, 0) < 0) {
if (errno != EINTR) {
status = -1; /* error other than EINTR from waitpid() */
break;
}
}
}
return(status);
}
#include
#include
void test_system()
{
int status;
char *cmdstr = "ls -al";
if ((status = system(cmdstr)) < 0) {
perror("system error");
return;
}
}
根据模式重定向子进程的标准输入或输出,提供控制子进程输入或输出的能力。
#include
FILE *popen(const char *cmdstring, const char *type);
int pclose(FILE *fp);
以下代码修改自《UNIX环境高级编程》和 glibc 中 popen 的实现。
#include
#include
#include
#include
#include /* for calloc*/
static pid_t *childpid = NULL;
static int maxfd = 1024;
FILE * popen(const char *cmd, const char * mode)
{
int do_read = 0, do_write = 0;
int parent_end, child_end;
int fds[2];
pid_t pid;
FILE *fp;
const char *type= mode;
while (*type != '\0') {
switch (*type++)
{
case 'r':
do_read = 1;
break;
case 'w':
do_write = 1;
break;
default:
return NULL;
}
}
if (do_read ^ do_write == 0)
return NULL;
if (pipe(fds) < 0)
return NULL;
if (do_read) {
parent_end = fds[0];
child_end = fds[1];
} else {
parent_end = fds[1];
child_end = fds[0];
}
if (childpid == NULL) {
if ((childpid = calloc(maxfd, sizeof(pid_t))) == NULL) {
perror("calloc error");
return NULL;
}
}
if ((pid = fork()) < 0)
return NULL;
else if (pid == 0) {
int child_std_end = do_read ? 1 : 0;
if (child_end != child_std_end)
dup2(child_end, child_std_end); /* clear close-on-exec flag of child_std_end */
close(child_end);
close(parent_end);
/*
* POSIX.1 要求 popen 关闭那些以前调用 popen 打开,
* 现在仍在子进程中打开的 I/O 流。
*/
for (int i = 0; i < maxfd; i++) {
if (childpid[i] > 0)
close(i);
}
execl("/bin/sh", "sh", "-c", cmd, (char *)0);
_exit(127); /* execl error */
}
close(child_end);
if ((fp = fdopen(parent_end, mode)) == NULL) {
perror("fail open fd");
return NULL;
}
childpid[fileno(fp)] = pid;
return fp;
}
int pclose(FILE* stream)
{
int pid, stat;
int fd;
if (childpid == NULL) {
errno = EINVAL;
return -1;
}
fd = fileno(stream);
if (fd >= maxfd) {
errno = EINVAL;
return -1;
}
if ((pid = childpid[fd]) == 0) {
errno = EINVAL;
return -1;
}
childpid[fd] = 0;
if (fclose(stream) == EOF) {
return -1;
}
while (waitpid(pid, &stat, 0) < 0) {
if (errno != EINTR) {
return -1;
}
}
return stat;
}
void test_popen()
{
FILE *fp;
char *data = "hello world!";
char buf[1024];
/* write example */
char *cmdstring = "wc -c"; /* 统计字符个数 */
if ((fp = popen(cmdstring, "w")) == NULL) {
perror("popen error");
return;
}
fputs(data, fp);
if (pclose(fp) < 0) {
perror("pclose error");
return;
}
/* read example */
cmdstring = "ls -al";
if ((fp = popen(cmdstring, "r")) == NULL) {
perror("popen error");
return;
}
for ( ; ; ) {
fflush(stdout);
if (fgets(buf, sizeof(buf), fp) == NULL)
break;
if (fputs(buf, stdout) == EOF) {
perror("fputs error");
return;
}
}
if (pclose(fp) < 0) {
perror("pclose error");
return;
}
}