1. system()
程序可以通过调用 system() 函数来执行任意的shell命令。同样这个Shell命令执行的输出会打印在终端上。\
#include
int system(const char* command);
system()库函数通过fork出一个子进程,并且让子进程通过execl()程序替换来执行对应的 shell 命令。问题是替换成什么程序可以执行shell命令呢?答案是 /bin 目录下的sh程序。sh -c command即可执行指定的命令。sh 其实就是一个shell程序,在我的系统中 sh 是一个指向 dash 的软链接,所以system()的真正执行步骤应该是:调用system(),函数内部创建了一个子进程并替换成 shell 程序,而 shell 程序执行一条命令的步骤同样是创建一个子进程,并让子进程替换成需要执行的 command,所以可以看到system()运行期间总共存在三个进程。下图展现了用system()执行sleep命令的步骤:
system()当命令执行完毕才会返回,也就是阻塞函数。
在命令执行期间,SIGCHLD信号将被阻塞,SIGINT和SIGQUIT信号将被忽略。
如果 command 为NULL,则system()返回一个表示当前系统是否有可用shell的状态。
2. 基本的 system() 实现
先按照文档的描述来实现一个简单的system()函数,这其实就是对Linux编程的基本知识如创建子进程、进程等待、进程替换的一个综合运用。
int my_system(const char* command) {
pid_t pid = fork();
int status;
switch(pid) {
case -1:
return -1;
case 0: // child
execl("/bin/sh", "sh", "-c", command, NULL);
exit(-1); // execl error
default: // parent
waitpid(pid, &status, 0);
return status;
}
}
3. popen() : 通过管道与Shell命令进行通信
popen()与system()的功能类似,同样是执行一条Shell命令。但popen()使用了管道来读取Shell命令的输出或者是通过管道来给Shell命令传递输入。
#include
FILE* popen(const char* command, const char* mode);
int pclose(FILE* stream);
popen()函数创建了一个管道,然后创建一个子进程来执行Shell,而Shell同样需要创建一个子进程来执行指定的Shell命令。这个过程类似于system()。mode 参数是一个字符串,它确定函数的调用者是从管道中读取 Shell 命令的输出(mode为 r ),还是通过管道给Shell命令传递输入(mode为 w )。(由于单个管道只支持单向信息传输,所以必须在创建管道之前就确定数据传输方向)。下图展示了两种mode取值的不同之处:
popen()返回了该管道的操作句柄,即一个文件流指针,函数调用者可以通过这个操作句柄来和Shell命令进行通信。这也点出了popen()与system()的不同之处,即system()是一个阻塞函数,Shell命令执行完成之前不会返回,用户通过终端与Shell命令交互。而popen()是一个非阻塞函数,它在建立一个子进程并执行程序替换sh -c command之后会立刻返回,而不等待Shell程序的终止。用户通过主程序与Shell命令进行通信。即可以总结为popen()的调用进程与Shell命令进程是并行执行的。
由于使用的是管道,所以与 pipe 创建出的管道有相同的读写特性,即管道写端关闭后尝试读会返回0,管道读端关闭后尝试写会受到 SIGPIPE 信号而被杀死。
一旦 I/O 结束后可以使用pclose()函数关闭管道,并且该函数会等待Shell程序终止才返回。而pclose()的返回值为Shell进程的终止状态,即 wait 所得到的状态。
4. 基本的popen() 实现
#include
#include
#include
FILE* my_popen(const char* command, const char* type) {
FILE* fp;
// if type is invalid
if((type[0] != 'w' && type[0] != 'r') || type[1] != '\0') {
return NULL;
}
int pipefd[2];
pipe(pipefd);
pid_t pid = fork();
switch(pid) {
case -1:
perror("fork error");
exit(0);
case 0: // child
if(type[0] == 'w') {
close(pipefd[1]);
dup2(pipefd[0], STDIN_FILENO);
close(pipefd[0]); // close extra read end
}
else if(type[0] == 'r') {
close(pipefd[0]);
dup2(pipefd[1], STDOUT_FILENO);
close(pipefd[1]); // close extra write end
}
execl("/bin/sh", "sh", "-c", command, NULL);
exit(-1);
default: // parent
if(type[0] == 'r') {
close(pipefd[1]);
fp = fdopen(pipefd[0], type);
}
else {
close(pipefd[0]);
fp = fdopen(pipefd[1], type);
}
return fp;
}
}