s在linux下我们应该尽量的避免使用system。现在我们就来看看linux下的system()函数的简单的介绍:
#include <stdlib.h>
int system(const char* command)
system()函数调用/bin/sh来执行参数的指定命令,/bin/sh一般是一个软连接,指向某个具体的shell,比如bash,-c选项是告诉shell从字符串command中读取命令;在command执行期间,SIGCHLD是被阻塞的,好比在说,hi,内核,这会不要给我送SIGCHLD信号,等我忙完再说;在该command期间,SIGCHLD和SIGQUIT是被忽略的,意思是进程收到这两个信号后没有任何动作。
我们再来看看system的返回值:
这里我们可能对system()可能理解的换不是很到位,我们需要了解它的执行的过程,实际上system()函数执行了三个操作:
1、fork一个子进程。
2、在子进程中调用exec函数去执行command;
3、在父进程中调用wait去等待子进程结束;对于fork失败,system函数返回的是-1,如果exec执行成功,也即command顺利执行完毕,则返回command通过exit或return返回的值。(注意command顺利执行不代表执行成功,比如command:“rm 的不够牢固.txt”,不管文件存不存在command都顺利执行了)如果exec执行失败,也即command没有顺利执行,比如信号中断,或者command命令根本不存在,system函数的返回值为1。如果command为NULL,则system函数的返回非0值,一般为1。
我满五年来看下system的一段源码:
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <errno.h>
4
5 int main(const char* cmdstring){//如果cmdstring为空,返回非零值,一般为1
6 pid_t pid;
7 int status;
8 if(cmdstring==NULL){
9 return (1);
10 }
11 if((pid=fork())<0){
12 status=-1;//fork失败,返回-1
13 }
14 else if(pid==-1){
15 execl("/bin/sh","sh","-c",cmdstring,(char*)0);
16 exit(127);//exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话,现在的进程就不存在了。
17 }
18 else
19 {
20 while(waitpid(pid,&status,0)<0)
21 {
22 if(errno != EINTR)
23 {
24 status=-1;//如果waitpid被信号中断,则返回-1
25 break;
26 }
27 }
28 }
29 return status;//如果waitpid等待成功,则返回子进程的返回状态。
30 }
我们在看完这个代码之后system()函数的简单实现,那么该函数的返回值就清晰了,那么什么时候system()函数的返回值0,只有command命令返回0时。
那么如何监控system()函数执行状态
system函数用起来容易出错,返回值太多,而且返回值很容易跟command的返回值混淆,这里我们推荐的是popen函数进行代替,关于popen函数的简单使用。我们先来做下简单的介绍:
popen()函数较于system()函数的优点在于使用简单,popen()函数只返回两个值。成功返回子进程的status,使用WIFEXITED相关宏就可以取得command的返回结果;失败返回-1,我们可以使用perro()函数或者strerror()函数得到有用的错误信息。
在标准I/O函数库提供了popen函数,它启动另外一个进程去执行一个shell命令,这里我们称调用popen的进程为父进程,由popen启动的进程称为子进程。popen函数还创建一个管道用于父子之间的通信,父进程要么从管道中读取信息,要么想管道中写信息,至于是读还是写,取决于父进程调用popen函数时传递的参数。
函数的功能:popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令,参数type可食用“r”代表读取,“w”代表写入。依照此type的值,popen会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。随后进程便可以利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中,返回值:若成功则返回文件指针,否则返回NULL,错误原因存在于errno中。
函数功能:pclose()函数用来关闭由popen所建立的管道及文件指针。参数stream为先由popen()所返回的指针文件。
返回值:若成功返回shell的终止状态(也即子进程的终止状态),若出错则返回-1,错误原因在errno中。
下面我们来看看popen的使用:
假如我们想取得当前目录下的文件个数,在shell下我们可以使用:
ls | wc -l
我们在程序中可以这样写:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <errno.h>
4 #include <sys/wait.h>
5 #include <string.h>
6
7 #define MAXLINE 1024
8
9 int main()
10 {
11 char result_buf[MAXLINE],command[MAXLINE];
12 int rc=0;//用于接收命令返回值
13 FILE* fp;
14 //将要执行的命令写入buf
15 snprintf(command,sizeof(command),"ls ./ |wc -l");
16 //执行预先设定的命令,并读出该命令的标准输出
17 fp=popen(command,"r");
18 if(NULL==fp){
19 perror("popen执行失败");
20 exit(1);
21 }
22 while(fgets(result_buf,sizeof(result_buf),fp)!=NULL)
23 {
24 if('\n'==result_buf[strlen(result_buf)-1]){
25 result_buf[strlen(result_buf)-1]='\0';
26 }
27 printf("命令【%s】输出【%s】\r\n",command,result_buf);
28 }
29 //等待命令执行完毕并关闭管道和文件指针
30 rc=pclose(fp);
31 if(-1==rc){
32 perror("关闭文件指针失败");
33 exit(1);
34 }
35 else{
36 printf("命令【%s】 子进程结束状态【%d】命令返回值【%d】\r\n",command,rc,WEXITSTATUS(rc));
37 }
38 return 0;
39 }
代码最后的执行结果为
上面popen只是捕获了command的标准输出,如果command命令执行失败了,子进程会将错误的信息打印到标准错误输出。父进程就无法获取,比如command命令为“ls nofile.txt”,事实上我们根本没有nofile.txt这个文件,这时shell会输出“ls :nofile.txt:No such file or directory”。这个输出是在标准错误上输出上的。通过上面的程序无法获取。
注意:如果你把上面的程序中的command设成“ls nofile.txt”,编译执行程序你会看见如下的结果:
需要注意的是输出的第一行并不是父进程的输出,而是子进程的标准错误输出。有时候子进程的错误信息是很有用的,那么父进程该如何获取错误信息呢?
在这里我们可以重定向子进程的错误输出,让错误重定向到标准输出(2>&1),这样父进程就可以捕捉子进程的错误信息。例如:command为“ls nofile.txt 2>&1”,输出如下:
附加:
子进程的终止状态判断设计到宏,设终止状态为status。
WIFEXITED(status) 如果子进程正常结束则为非0值。
WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED来判断是否正常结束才能使用该宏。
WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真。
WTERMSIG(status)取得子进程因信号而终止的信号代码,一般会先用WIFSIGNALED来判断后,才使用此宏。
WIFSTOPPED(status)如果子进程处于暂停执行情况,则此宏值为真,一般只有使用WUNTRACED时才会有此情况。
WSTOSIG(status)取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED来判断后才能使用此宏。
总结:
fork用来创建一个子进程 一个程序一调用fork函数,首先,系统让新的进程与旧的进程使用同一个代码段,因为它们的程序还是相同的,对于数据段和堆栈段,系统则复制一份给新的进程,这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。而如果两个进程要共享什么数据的话,就要使用另一套函数(shmget,shmat,shmdt等)来操作。现在,已经是两个进程了,对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零,这样,对于程序,只要判断fork函数的返回值,就知道自己是处于父进程还是子进程中。 system 可以看做是fork + execl + waitpid。system()函数,功能强大 当system接受的命令为NULL时直接返回,否则fork出一个子进程,因为fork在两个进程:父进程和子进程中都返回,这里要检查返回的pid,fork在子进程中返回0,在父进程中返回子进程的pid,父进程使用waitpid等待子进程结束,子进程则是调用execl来启动一个程序代替自己,execl(“/bin/sh”, “sh”, “-c”, cmdstring, (char*)0)是调用shell,这个shell的路径是/bin/sh,后面的字符串都是参数,然后子进程就变成了一个shell进程,这个shell的参数是cmdstring,就是system接受的参数。在windows中的shell是command,想必大家很熟悉shell接受命令之后做的事了。 popen()也常常被用来执行一个程序
popen() 函数用创建管道的方式启动一个 进程, 并调用 shell. 因为管道是被定义成单向的, 所以 type 参数只能定义成只读或者只写, 不能是两者同时, 结果流也相应的是只读或者只写. command 参数是一个字符串指针, 指向的是一个以 null 结束符结尾的字符串, 这个字符串包含一个 shell 命令. 这个命令被送到 /bin/sh 以 -c 参数执行, 即由 shell 来执行. type 参数也是一个指向以 null 结束符结尾的字符串的指针, 这个字符串必须是 ‘r‘ 或者 ‘w’ 来指明是读还是写.
popen() 函数的返回值是一个普通的标准I/O流, 它只能用 pclose() 函数来关闭, 而不是 fclose() 函数. 向这个流的写入被转化为对 command 命令的标准输入; 而 command 命令的标准输出则是和调用 popen(), 函数的进程相同,除非这个被command命令自己改变. 相反的, 读取一个 “被popen了的” 流, 就相当于读取 command 命令的标准输出, 而 command 的标准输入则是和调用 popen, 函数的进程相同.