linux下system函数详解
⼀、system函数的简单介绍
头⽂件
#include <stdlib.h>
函数定义
int system(const char * string);
函数说明
system()会调⽤fork()产⽣⼦进程,由⼦进程来调⽤/bin/sh-c string来执⾏参数string字符串所代表的命令。此命
令执⾏完后随即返回原调⽤的进程。在调⽤system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信
号则会被忽略。
⼆、system函数的返回值
status = system("./test.sh")
1、先统⼀两个说法:
(1)system返回值:指调⽤system函数后的返回值,⽐如上例中status为system返回值
(2)shell返回值:指system所调⽤的shell命令的返回值,⽐如上例中,test.sh中返回的值为shell返回值。
2、man中对于system的说明
RETURN VALUE
The value returned is -1 on error (e.g. fork() failed), and the return status of the command otherwi.
This latter return status is in the format specified in wait(2). Thus, the exit code of the command
will be WEXITSTATUS(status). In ca /bin/sh could not be executed, the exit status will be that o
f a
command that does exit(127).
system函数对返回值的处理,涉及3个阶段:
阶段1:创建⼦进程等准备⼯作。如果失败,返回-1。
阶段2:调⽤/bin/sh拉起shell脚本,如果拉起失败或者shell未正常执⾏结束(参见备注1),原因值被写⼊到status的低8~15⽐特位中。system的man中只说明了会写了127这个值,但实测发现还会写126等值。
阶段3:如果shell脚本正常执⾏结束,将shell返回值填到status的低8~15⽐特位中。
只要能够调⽤到/bin/sh,并且执⾏shell过程中没有被其他信号异常中断,都算正常结束。⽐如:不管shell脚本中返回什么原因值,是0还是⾮0,都算正常执⾏结束。即使shell脚本不存在或没有执⾏权限,也都算正常执⾏结束。如果shell脚本执⾏过程中被强制kill掉等情况则算异常结束。如何判断阶段2中,shell脚本是否正常执⾏结束呢?系统提供了宏:WIFEXITED(status)。如果WIFEXITED(status)为真,则说明正常结束。如何取得阶段3中的shell返回值?你可以直接通过右移8bit来实现,但安全的做法是使⽤系统提供的宏:WEXITSTATUS(status)。
由于我们⼀般在shell脚本中会通过返回值判断本脚本是否正常执⾏,如果成功返回0,失败返回正数。所以综上,判断⼀个system函数调⽤shell脚本是否正常结束的⽅法应该是如下3个条件同时成⽴:
(1)-1 != status
(2)WIFEXITED(status)为真
(3)0 == WEXITSTATUS(status)
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
pid_t status;
status = system("./test.sh");
if (-1 == status)
{
printf("system error!");
}
el
腾冲旅游攻略{
printf("exit status value = [0x%x]\n", status);
if (WIFEXITED(status))
{
if (0 == WEXITSTATUS(status))
{
printf("run shell script successfully.\n");
}
el
{
printf("run shell script fail, script exit code: %d\n", WEXITSTATUS(status));
}
}
el
{
printf("exit status = [%d]\n", WEXITSTATUS(status));
}
}
return 0;
}
为了更好的理解system()函数返回值,需要了解其执⾏过程,实际上system()函数执⾏了三步操作:
1.fork⼀个⼦进程;
2.在⼦进程中调⽤exec函数去执⾏command;
3.在⽗进程中调⽤wait去等待⼦进程结束。
对于fork失败,system()函数返回-1。 如果exec执⾏成功,也即command顺利执⾏完毕,则返回command通过exit或return返回的值。 (注意,command顺利执⾏不代表执⾏成功,⽐如command:“”,不管⽂件存不存在该command都顺利执⾏了) 如果exec执⾏失败,
也即command没有顺利执⾏,⽐如被信号中断,或者command命令根本不存在,system()函数返回127. 如果command为NULL,则system()函数返回⾮0值,⼀般为1.
看完这些,我想肯定有⼈对system()函数返回值还是不清楚,看源码最清楚,下⾯给出⼀个system()函数的实现:
int system(const char * cmdstring)
{
宠妃十五夜pid_t pid;
int status;
if(cmdstring == NULL)
{
return (1); //如果cmdstring为空,返回⾮零值,⼀般为1
}
if((pid = fork())<0)
{
status = -1; //fork失败,返回-1
} el if(pid == 0)
{
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127); // exec执⾏失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在啦~~
}
el //⽗进程
{
while(waitpid(pid, &status, 0) < 0)
{
if(errno != EINTR)
{
status = -1; //如果waitpid被信号中断,则返回-1
break;
}
}
七的繁体}
return status; //如果waitpid成功,则返回⼦进程的返回状态
}
三、调⽤system函数的问题
先看⼀下问题 简单封装了⼀下system()函数:
int pox_system(const char *cmd_line)
{
return system(cmd_line);
}
函数调⽤:
int ret = 0;
ret = pox_system("gzip -c /var/l > /var/opt/I00005.z");
if(0 != ret)
{
Log("zip file failed\n");
}
问题现象:每次执⾏到此处,都会zip failed。⽽单独把该命令拿出来在shell⾥执⾏却总是对的,事实上该段代码已运⾏了很长时间,从没出过问题。
分析log时,我们只能看到“zip file failed”这个我们⾃定义的信息,⾄于为什么fail,毫⽆线索。 那好,我们先试着找出更多的线索:
int ret = 0;
ret = pox_system("gzip -c /var/l > /var/opt/I00005.z");
if(0 != ret)
{
Log("zip file failed: %s\n", strerror(errno)); //尝试打印出系统错误信息
}
我们增加了log,通过system()函数设置的errno,我们得到⼀个⾮常有⽤的线索:system()函数失败是
由于“ No child process”。我们通过上⾯的线索,知道system()函数设置了errno为ECHILD,然⽽从system()函数的man⼿册⾥我们找不到任何有关EHILD的信息。我们知道system()函数执⾏过程为:fork()->exec()->waitpid(). 很显然waitpid()有重⼤嫌疑,我们去查⼀下man⼿册,看该函数有没有可能设置
ECHILD: ECHILD (for waitpid() or waitid()) The process specified by pid (waitpid()) or idtype and id (waitid())
does not exist or is not a child of the calling process. (This can happen for one's own child if the action for
SIGCHLD is t to SIG_IGN. See also the Linux Notes ction about threads.)
果然有料,如果SIGCHLD信号⾏为被设置为SIG_IGN时,waitpid()函数有可能因为找不到⼦进程⽽报ECHILD错误。似乎我们找到了问题的解决⽅案:在调⽤system()函数前重新设置SIGCHLD信号为缺省值,即signal(SIGCHLD, SIG_DFL)。
system()函数之前没出错,是因为systeme()函数依赖了系统的⼀个特性,那就是内核初始化进程时对SIGCHLD信号的处理⽅式为
SIG_DFL,这是什么什么意思呢?即内核发现进程的⼦进程终⽌后给进程发送⼀个SIGCHLD信号,进程收到该信号后采⽤SIG_DFL⽅式处理,那么SIG_DFL⼜是什么⽅式呢?SIG_DFL是⼀个宏,定义了⼀个信号处理函数指针,事实上该信号处理函数什么也没做。这个特性正是system()函数需要的,system()函数⾸先fork()⼀个⼦进程执⾏command命令,执⾏完后system()函数会使⽤waitpid()函数对⼦进程进⾏收⼫。 通过上⾯的分析,我们可以清醒的得知,system()执⾏前,SIGCHLD信号的处理⽅式肯定变了,不再是SIG_DFL了,⾄于变成什么暂时不知道,事实上,我们也不需要知道,我们只需要记得使⽤system()函数前把SIGCHLD信号处理⽅式显式修改为SIG_DFL⽅式,同时记录原来的处理⽅式,使⽤完system()后再设为原来的处理⽅式。
问题分析到这⾥,解决⽅法也清晰了,于是我们修改了我们的pox_system()函数:
typedef void (*sighandler_t)(int);
int pox_system(const char *cmd_line)
{
int ret = 0;
sighandler_t old_handler;
old_handler = signal(SIGCHLD, SIG_DFL);
ret = system(cmd_line);
signal(SIGCHLD, old_handler);
海伦凯勒读后感return ret;
}
四、了解popen函数
标准I/O函数库提供了popen函数,它启动另外⼀个进程去执⾏⼀个shell命令⾏。
这⾥我们称调⽤popen的进程为⽗进程,由popen启动的进程称为⼦进程。
popen函数还创建⼀个管道⽤于⽗⼦进程间通信。⽗进程要么从管道读信息,要么向管道写信息,⾄于是读还是写取决于⽗进程调⽤popen 时传递的参数。下在给出popen、pclo的定义。
#include <stdio.h>
/*
函数功能:popen()会调⽤fork()产⽣⼦进程,然后从⼦进程中调⽤/bin/sh -c来执⾏参数command的指令。
参数type可使⽤“r”代表读取,“w”代表写⼊。
依照此type值,popen()会建⽴管道连到⼦进程的标准输出设备或标准输⼊设备,然后返回⼀个⽂件指针。
随后进程便可利⽤此⽂件指针来读取⼦进程的输出设备或是写⼊到⼦进程的标准输⼊设备中蜂胶功效
返回值:若成功则返回⽂件指针,否则返回NULL,错误原因存于errno中
*/
FILE * popen( const char * command,const char * type);
/*
函数功能:pclo()⽤来关闭由popen所建⽴的管道及⽂件指针。参数stream为先前由popen()所返回的⽂件指针
返回值:若成功返回shell的终⽌状态(也即⼦进程的终⽌状态),若出错返回-1,错误原因存于errno中
*/
int pclo(FILE * stream);
下⾯通过例⼦看下popen的使⽤:
假如我们想取得当前⽬录下的⽂件个数,在shell下我们可以使⽤:
ls | wc -l
我们可以在程序中这样写:
/*取得当前⽬录下的⽂件个数*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#define MAXLINE 1024
int main()
{
char result_buf[MAXLINE], command[MAXLINE];
int rc = 0; // ⽤于接收命令返回值
FILE *fp;
/*将要执⾏的命令写⼊buf*/
snprintf(command, sizeof(command), "ls ./ | wc -l");
/*执⾏预先设定的命令,并读出该命令的标准输出*/
fp = popen(command, "r");
if(NULL == fp)
{
perror("popen执⾏失败!");
工作汇报材料exit(1);
}
while(fgets(result_buf, sizeof(result_buf), fp) != NULL)
{
/*为了下⾯输出好看些,把命令返回的换⾏符去掉*/
if('\n' == result_buf[strlen(result_buf)-1])
{
result_buf[strlen(result_buf)-1] = '\0';
}自制雪糕
printf("命令【%s】输出【%s】\r\n", command, result_buf);
}
/*等待命令执⾏完毕并关闭管道及⽂件指针*/
rc = pclo(fp);
if(-1 == rc)
{
perror("关闭⽂件指针失败");
exit(1);
}
el
{
printf("命令【%s】⼦进程结束状态【%d】命令返回值【%d】\r\n", command, rc, WEXITSTATUS(rc));
}
return 0;
}
编译并执⾏:
$ gcc popen.c
仅仅有条和井井有条的区别$ ./a.out
命令【ls ./ | wc -l】 输出【2】
命令【ls ./ | wc -l】⼦进程结束状态【0】命令返回值【0】
上⾯popen只捕获了command的标准输出,如果command执⾏失败,⼦进程会把错误信息打印到标准错误输出,⽗进程就⽆法获取。⽐如,command命令为“” ,事实上我们根本没有这个⽂件,这时shell会输出“ls: : No such file or directory”。这个输出是在标准错误输出上的。通过上⾯的程序并⽆法获取。
注:如果你把上⾯程序中的command设成“”,编译执⾏程序你会看到如下结果: