上一节使用基于 linux 中的信号机制,使用 C语言实现了类似于 python 的 try 语句,使得我们的 C语言程序也能够处理 8/0 这种 0 做除数的无意义问题,也能用其来捕捉令人头疼的段错误等崩溃性错误。
system 函数
本节再回到 linux,来看看另一个比较有意思的C语言函数——system函数。system 的函数原型如下:
它可以执行一条由参数 command 指定的 shell 命令,调用 system(command) 函数和在 shell 中执行下面的命令是相似的:
# /bin/sh -c command
现在来写一个 C语言程序测试一下 system 函数:
#include <stdio.h>
#include <stdlib.h>
int main()
{
system("touch test_system");
return 0;
}
按照分析,以上代码应该就相当于在 shell 中执行了
# touch test_system
也即在当前目录创建了 test_system 文件。编译并执行,发现与预期一致,C语言程序的确在当前目录下创建了 test_system 文件:
分析 system 函数,并使用 C 语言实现它
以上C语言代码并没有关心 system 函数的返回值,这在实际开发中是不严谨的。那么,system 函数的返回值是什么呢?继续查看手册,会发现 system 函数应该是由 fork,wait,exec 函数族实现的,它的返回值就与这三个函数相关。
所以如果 fork 失败,system 函数会返回 -1。如果 wait 失败,则会返回 WEXITSTATUS(status),status 是 shell 命令的返回状态码。如果传给 system 的 shell 命令执行失败,则就相当于 shell 执行了 exit(127)。
fork,wait,exec 函数族我们之前都介绍过。现在仔细思考一下,system 函数的实现应该不难,无非就是 fork 出一个子进程调用 exec 函数族执行 shell 命令,然后在父进程里 wait 子进程返回。现在使用 C语言代码实现之,请看:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int mysystem(char* cmd)
{
pid_t pid;
int ret;
if(NULL==cmd)
return 1;
pid = fork();
switch(pid){
case -1:
ret = -1;
break;
case 0:
execl("/bin/sh", "sh", "-c", cmd, NULL);
exit(127);
default:
while(waitpid(pid, &ret, 0)<0);
break;
}
return ret;
}
int main()
{
mysystem("touch test_system_by_my_system");
return 0;
}
代码还是非常简单的,唯一值得说明的是 case 0,execl 函数如果执行成功,就会替换当前进程,exit 函数也就没机会执行。exit 只有在 execl 函数执行失败时才有可能执行。现在编译执行,发现的确成功了:
需要说明的是,这里实现的 mysystem 还没有考虑信号处理。
使用 system 注意事项
根据 man 手册,system 不应用于设置用户 ID 或者组 ID,这么做可能会有奇怪的现象发生(恶意用户可能会修改 system 的执行环境)。如果希望修改用户 ID 或者组 ID,则应使用 exec 函数族代替(不能是 execlp 和 execvp 函数)。实际上,v2 版本的 bash 已经弃用 system 修改设置用户 ID 或者组 ID 的功能了。