前言
fork()创建个子进程都是让子进程继续执行父进程的文本段,但更多的情况下是让该进程去执行另外一个程序,本文分别介绍fork()+exec*()和popen()去执行另一程序的方法。
一、exec*()执行另一程序
exec*()一系列函数的原型如下:
int execl(const char *path, const char *arg, …);
int execlp(const char *file, const char *arg, …);
int execle(const char *path, const char *arg, …, char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
…
- l 表示以列表(list)的形式传递要执行程序的命令行参数
- v表示以数组(vector)的形式传递要执行程序的命令行参数
- e表示给该命令传递环境变量(environment)。
代码示例
执行ifconfig命令可以获取IP地址,显示如下:
以下为fork()+execl()执行ifconfig命令获取IP地址代码示例:
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<ctype.h>
#define TMP_FILE "/tmp/.ifconfig.log" // 标准输出重定向的文件, /tmp路径是在Linux系统在内存里做的一个文件系统
int main(int argc, char **argv)
{
pid_t pid;
int fd;
char buf[1024];
int rv;
char *ptr;
FILE *fp;
char *ip_start;
char *ip_end;
char ipaddr[16];
if( (fd=open(TMP_FILE,O_RDWR|O_CREAT|O_TRUNC,0644)) < 0 ) //打开/tmp/.ifconfig.log文件
{
printf("Redirect standard output to file failure:%s\n",strerror(errno));
return -1;
}
pid = fork(); //创建子进程
if( pid < 0 ) //创建子进程失败
{
printf("fork() create process failure:%s\n",strerror(errno));
return -1;
}
else if( 0 == pid ) //子进程正在运行
{
printf("Child process start excute ifconfig program\n");
dup2(fd,STDOUT_FILENO); //输出重定向
execl("/sbin/ifconfig","ifconfig","eth0",NULL); //调用execl()函数,/sbin/ifconfig为要执行文件的路径,ifconfig和eth0为执行文件时要传递的参数,最后一个参数以NULL结尾
printf("Child process excute another program,will not return here.Return here means execl() error\n");
return -1;
}
else //父进程正在运行
{
sleep(3); //让子进程先运行
}
//子进程调用了execl(), 会丢掉父进程的文本段,不会执行到这里,只有父进程会执行后面的代码
memset(buf,0,sizeof(buf)); //清空buf[]
rv=read(fd,buf,sizeof(buf)); //把前面打开的文件写入buf中
printf("Read %d bytes data dierectly read after child process write\n",rv); //此时读取的字节数应为0,因为子进程往文件里写内容时已经将文件偏移量修改到文件尾
memset(buf,0,sizeof(buf)); //清空buf[]
lseek(fd,0,SEEK_SET); //把光标置于文件首
rv=read(fd,buf,sizeof(buf)); //把fd内容读到buf[]中
printf("Read %d bytes data after lseek:\n%s",rv,buf); //此时读取的字节数应为全部字节
// 使用read()一次性读N多个字节进buf[],但有时我们希望一行一行地读取文件内容,这时可以使用fdopen()将文件描述符fd转成文件流fp
fp = fdopen(fd,"r");
fseek(fp,0,SEEK_SET); //重新把光标放于文件首
while(fgets(buf,sizeof(buf),fp)) //fgets()一次读一行,读到文件尾则返回NULL
{
if( strstr(buf,"netmask") ) //寻找含有字符串netmask的一行
{
ptr = strstr(buf,"inet"); //查找inet关键字
if( !ptr )
{
break;
}
ptr += strlen("inet");
while( isblank(*ptr) ) //判断是否为空白符
ptr ++;
ip_start = ptr; //空白符后为IP地址的起始地址
while( !isblank(*ptr) )
ptr ++;
ip_end =ptr; //IP后第一个空白符为IP地址的末尾地址
memset(ipaddr,0,sizeof(ipaddr));
memcpy(ipaddr,ip_start,ip_end-ip_start); //将IP地址拷贝到ipaddr中,ip_end-ip_start是IP地址的长度,ip_start是IP地址的起始置
break;
}
}
printf("Parser and get IP address:%s\n",ipaddr);
fclose(fp);
unlink(TMP_FILE);
return 0;
}
程序运行结果:
二、popen()执行另一程序
函数popen()可以执行一条命令,并返回一个基于管道(pipe)的文件流,我们可以从该文件流中一行样解析所需要的东西。
代码示例
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<ctype.h>
int get_ipaddr(char *interface,char *ipaddr,int ipaddr_size); //函数声明
int main(int argc,char **argv)
{
char ipaddr[16];
char *interface="eth0";
memset(ipaddr,0,sizeof(ipaddr)); //清空ipaddr
if( get_ipaddr(interface,ipaddr,sizeof(ipaddr)) < 0) //调用get_ipaddr函数
{
printf("ERROR:get IP address failure\n");
return -1;
}
printf("get network interface %s IP address [%s]\n",interface,ipaddr);
return 0;
}
int get_ipaddr(char *interface,char *ipaddr,int ipaddr_size)
{
char buf[1024];
char *ptr;
char *ip_start;
char *ip_end;
FILE *fp;
int len;
int rv;
if( !interface || !ipaddr || ipaddr_size<16 )
{
printf("Invalid input arguments\n");
return -1;
}
memset(buf,0,sizeof(buf));
snprintf(buf,sizeof(buf),"ifconfig %s",interface);
if( NULL == (fp=popen(buf,"r")) )
{
printf("popen() to excute command \"%s\" failure:%s\n",buf,strerror(errno));
return -2;
}
rv = -3;
while( fgets(buf,sizeof(buf),fp) )
{
if( strstr(buf,"netmask") )
{
ptr=strstr(buf,"inet");
if( !ptr )
{
break;
}
ptr += strlen("inet");
while( isblank(*ptr) )
ptr++;
ip_start=ptr;
while( !isblank(*ptr) )
ptr++;
ip_end=ptr;
memset(ipaddr,0,sizeof(ipaddr));
len = ip_end - ip_start;
len = len>ipaddr_size ? ipaddr_size :len;
memcpy(ipaddr,ip_start,len);
rv = 0;
break;
}
}
return rv;
}
程序运行结果:
总结
使用fork()+exec*()或popen()都可以让进程去执行另外一条命令,但两者相比,使用函数popen()更为简单一点。