您的位置:首页 > 运维架构 > Linux

Linux下system () 函数详解简介

2016-12-21 10:14 351 查看


(执行shell命令)

  相关函数

  fork,execve,waitpid,popen

  表头文件

  #include<stdlib.h>

  定义函数

  intsystem(constchar*string);

  函数说明

  system()会调用fork()产生子进程,由子进程来调用/bin/sh-cstring来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD信号会被暂时搁置,SIGINT和SIGQUIT信号则会被忽略。

  返回值

  如果fork()失败返回-1:出现错误

  如果exec()失败,表示不能执行Shell,返回值相当于Shell执行了exit(127)

  如果执行成功则返回子Shell的终止状态

  如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数string为空指针(NULL),则返回非零值>。如果system()调用成功则最后会返回执行shell命令后的返回值,但是此返回值也有可能为system()调用/bin/sh失败所返回的127,因此最好能再检查errno来确认执行成功。

  附加说明

  在编写具有SUID/SGID权限的程序时请勿使用system(),system()会继承环境变量,通过环境变量可能会造成系统安全的问题。


System与exec的区别

  1、system()和exec()都可以执行进程外的命令,system是在原进程上开辟了一个新的进程,但是exec是用新进程(命令)覆盖了原有的进程

  2、system()和exec()都有能产生返回值,system的返回值并不影响原有进程,但是exec的返回值影响了原进程

system()函数功能强大,很多人用却对它的原理知之甚少先看linux版system函数的源码:



#include<sys/types.h>
#include<sys/wait.h>
#include<errno.h>
#include<unistd.h>

intsystem(constchar*cmdstring)
{
pid_tpid;
intstatus;

if(cmdstring==NULL){

return(1);
}

if((pid=fork())<0){

status=-1;
}elseif(pid=0){

execl("/bin/sh","sh","-c",cmdstring,(char*)0);

-exit(127);//子进程正常执行则不会执行此语句

}else{
while(waitpid(pid,&status,0)<0){

if(errno!=EINTER){

status=-1;

break;

}
}
}
returnstatus;
}




先分析一下原理,然后再看上面的代码大家估计就能看懂了:

当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接受命令之后做的事了。

如果上面的你没有看懂,那我再解释下fork的原理:当一个进程A调用fork时,系统内核创建一个新的进程B,并将A的内存映像复制到B的进程空间中,因为A和B是一样的,那么他们怎么知道自己是父进程还是子进程呢,看fork的返回值就知道,上面也说了fork在子进程中返回0,在父进程中返回子进程的pid。

windows中的情况也类似,就是execl换了个又臭又长的名字,参数名也换的看了让人发晕的,我在MSDN中找到了原型,给大家看看:



HINSTANCEShellExecute(
HWNDhwnd,
LPCTSTRlpVerb,
LPCTSTRlpFile,
LPCTSTRlpParameters,
LPCTSTRlpDirectory,
INTnShowCmd
);




用法见下:

ShellExecute(NULL,"open","c:\\a.reg",NULL,NULL,SW_SHOWNORMAL);


你也许会奇怪ShellExecute中有个用来传递父进程环境变量的参数lpDirectory,linux中的execl却没有,这是因为execl是编译器的函数(在一定程度上隐藏具体系统实现),在linux中它会接着产生一个linux系统的调用execve,原型见下:

intexecve(constchar*file,constchar**argv,constchar**envp);


看到这里你就会明白为什么system()会接受父进程的环境变量,但是用system改变环境变量后,system一返回主函数还是没变。原因从system的实现可以看到,它是通过产生新进程实现的,从我的分析中可以看到父进程和子进程间没有进程通信,子进程自然改变不了父进程的环境变量。希望小菜们不要拿tc或使用tc库的其他编译器中的system的调用结果来反驳我,这不是一个概念,DOS早死翘翘了,玩linux吧。就说到这里了。

c语言调用Linux的命令system("");popen

exec系列

execl等等类似的函数都可以执行任何shell下的命令。

#include<stdlib.h>

intexecl(constchar*path,constchar*arg,...);

intexeclp(constchar*file,constchar*arg,...);

intexecle(constchar*path,constchar*arg,...,char*constenvp[]);

intexecv(constchar*path,char*constargv[]);

intexecvp(constchar*file,char*constargv[]);

也可以,不同之处是前者可以执行完毕而不退出,后者执行完毕,正确会推出,错误则不退出,继续下面的语句。


【C/C++】Linux下使用system()函数一定要谨慎

http://my.oschina.net/renhc/blog/53580

曾经的曾经,被system()函数折磨过,之所以这样,是因为对system()函数了解不够深入。只是简单的知道用这个函数执行一个系统命令,这远远不够,它的返回值、它所执行命令的返回值以及命令执行失败原因如何定位,这才是重点。当初因为这个函数风险较多,故抛弃不用,改用其他的方法。这里先不说我用了什么方法,这里必须要搞懂system()函数,因为还是有很多人用了system()函数,有时你不得不面对它。

先来看一下system()函数的简单介绍:

view
source

print?

1
#include
<stdlib.h>
2
int
system
(
const
char
*command);
system()executesacommandspecifiedincommandbycalling/bin/sh-ccommand,andreturnsafterthecommandhasbeencompleted.Duringexecutionofthecommand,SIGCHLDwill
beblocked,andSIGINTandSIGQUITwillbeignored.

system()函数调用/bin/sh来执行参数指定的命令,/bin/sh一般是一个软连接,指向某个具体的shell,比如bash,-c选项是告诉shell从字符串command中读取命令;

在该command执行期间,SIGCHLD是被阻塞的,好比在说:hi,内核,这会不要给我送SIGCHLD信号,等我忙完再说;

在该command执行期间,SIGINT和SIGQUIT是被忽略的,意思是进程收到这两个信号后没有任何动作。

再来看一下system()函数返回值:

Thevaluereturnedis-1onerror(e.g.fork(2)failed),andthereturnstatusofthecommandotherwise.Thislatterreturnstatusisinthe
formatspecifiedinwait(2).Thus,theexitcodeofthecommandwillbeWEXITSTATUS(status).Incase/bin/shcouldnotbeexecuted,theexitstatuswillbethatofacommandthatdoesexit(127).
IfthevalueofcommandisNULL,system()returnsnonzeroiftheshellisavailable,andzeroifnot.

为了更好的理解system()函数返回值,需要了解其执行过程,实际上system()函数执行了三步操作:

1.fork一个子进程;

2.在子进程中调用exec函数去执行command;

3.在父进程中调用wait去等待子进程结束。

对于fork失败,system()函数返回-1。

如果exec执行成功,也即command顺利执行完毕,则返回command通过exit或return返回的值。

(注意,command顺利执行不代表执行成功,比如command:"rmdebuglog.txt",不管文件存不存在该command都顺利执行了)

如果exec执行失败,也即command没有顺利执行,比如被信号中断,或者command命令根本不存在,system()函数返回127.

如果command为NULL,则system()函数返回非0值,一般为1.

看一下system()函数的源码

看完这些,我想肯定有人对system()函数返回值还是不清楚,看源码最清楚,下面给出一个system()函数的实现:

view
source

print?

01
int
system
(
const
char
*
cmdstring)
02
{
03
pid_t
pid;
04
int
status;
05
06
if
(cmdstring
==NULL)
07
{
08
return
(1);
//如果cmdstring为空,返回非零值,一般为1
09
}
10
11
if
((pid
=fork())<0)
12
{
13
status
=-1;
//fork失败,返回-1
14
}
15
else
if
(pid
==0)
16
{
17
execl(
"/bin/sh"
,
"sh"
,
"-c"
,
cmdstring,(
char
*)0);
18
_exit(127);
//
exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在啦~~
19
}
20
else
//父进程
21
{
22
while
(waitpid(pid,
&status,0)<0)
23
{
24
if
(
errno
!=
EINTR)
25
{
26
status
=-1;
//如果waitpid被信号中断,则返回-1
27
break
;
28
}
29
}
30
}
31
32
return
status;
//如果waitpid成功,则返回子进程的返回状态
33
}
仔细看完这个system()函数的简单实现,那么该函数的返回值就清晰了吧,那么什么时候system()函数返回0呢?只在command命令返回0时。

看一下该怎么监控system()函数执行状态

这里给我出的做法:

view
source

print?

01
int
status;
02
if
(NULL
==cmdstring)
//如果cmdstring为空趁早闪退吧,尽管system()函数也能处理空指针
03
{
04
return
XXX;
05
}
06
status
=
system
(cmdstring);
07
if
(status
<0)
08
{
09
printf
(
"cmd:
%s\terror:%s"
,
cmdstring,
strerror
(
errno
));
//
这里务必要把errno信息输出或记入Log
10
return
XXX;
11
}
12
13
if
(WIFEXITED(status))
14
{
15
printf
(
"normal
termination,exitstatus=%d\n"
,
WEXITSTATUS(status));
//取得cmdstring执行结果
16
}
17
else
if
(WIFSIGNALED(status))
18
{
19
printf
(
"abnormal
termination,signalnumber=%d\n"
,
WTERMSIG(status));
//如果cmdstring被信号中断,取得信号值
20
}
21
else
if
(WIFSTOPPED(status))
22
{
23
printf
(
"process
stopped,signalnumber=%d\n"
,
WSTOPSIG(status));
//如果cmdstring被信号暂停执行,取得信号值
24
}
到于取得子进程返回值的相关介绍可以参考另一篇文章:http://my.oschina.net/renhc/blog/35116

system()函数用起来很容易出错,返回值太多,而且返回值很容易跟command的返回值混淆。这里推荐使用popen()函数替代,关于popen()函数的简单使用也可以通过上面的链接查看。

popen()函数较于system()函数的优势在于使用简单,popen()函数只返回两个值:

成功返回子进程的status,使用WIFEXITED相关宏就可以取得command的返回结果;

失败返回-1,我们可以使用perro()函数或strerror()函数得到有用的错误信息。

这篇文章只涉及了system()函数的简单使用,还没有谈及SIGCHLD、SIGINT和SIGQUIT对system()函数的影响,事实上,之所以今天写这篇文章,是因为项目中因有人使用了system()函数而造成了很严重的事故。现像是system()函数执行时会产生一个错误:“Nochildprocesses”。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux shell