C程序调用shell脚本共同拥有三种法子 :system()、popen()、exec系列数call_exec1.c。
system() 不用你自己去产生进程。它已经封装了,直接增加自己的命令。
exec 须要你自己 fork 进程,然后exec 自己的命令。
popen() 也能够实现运行你的命令,比system 开销小。
int system(const char *cmdstring);
cmdstring是一个字符指针,就是一个包含需要运行的shell命令的字符串
system()会调用fork()产生 子历程,由子历程来调用/bin/sh-c string来履行 参数string字符串所代表的命令,此命令履行 完后随即返回原调用的历程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被漠视 。
返回值:如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数string为空指针(NULL),则返回非零值。 如果 system()调用成功 则最后会返回履行 shell命令后的返回值,但是此返回值也有可能为system()调用/bin/sh失败所返回的127,因此最好能再反省 errno 来确认履行成功 。
system命令以其简略 高效的作用得到很很广泛的利用。
system函数的一种实现方式:
int system(const char *cmdstring)
{
pid_t pid;
int status;
if (cmdstring == NULL)
{
return (1);
}
if ((pid = fork()) < 0)
{
status = -1;
}
else if (pid == 0)
{
/* 本质上还是execl函数 */
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127);
}
else
{
while (waitpid(pid, &status, 0) < 0)
{
if (errno != EINTR)
{
status = -1;
break;
}
}
}
return(status);
}
常见操作是创建一个连接到另一个进程(shell的命令行)的管道,然后读其输出或向其输入端发送数据。
popen() 会调用fork()产生子历程,然后从子历程中调用/bin/sh -c来履行 參数command的指令。參数type可使用 “r”代表读取。“w”代表写入。遵循此type值,popen()会建立管道连到子历程的标准输出设备或标准输入设备,然后返回一个文件指针。
随后历程便可利用此文件指针来读取子历程的输出设备或是写入到子历程的标准输入设备中。此外,所有使用文件指针(FILE*)操作的函数都可以使用,除了fclose()以外。
返回值:若成功 则返回文件指针,否则返回NULL,差错 原因存于errno中。注意:在编写具SUID/SGID权限的程序时请尽量避免应用 popen()。popen()会继承环境变量。通过环境变量可能会造成系统安全的问题
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
工作原理:popen先执行fork,然后调用exec执行cmdstring,并返回一个标准的I/O文件指针。
所在头文件:stdio.h
cmdstring:包含shell命令字符串
type:为”r”时,则文件指针连接到cmdstring的标准输出,也就是代表指向执行shell命令返回的消息,也可以认为链接到stdout
为”w”时,则文件指针连接到cmdstring的标准输入,也可以认为链接到stdin
调用exec函数时,该调用ecec的进程执行的程序完全替换为新程序,但并不创建新进程,前后进程的ID并不改变。exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。
#include
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
const char *path要写绝对路径,const char *file就不用
最后一个参数为NULL 或者(char *)0
成功不会返回任何值,调用失败会返回-1
execlp("ls","ls","-l",NULL);
execl("/bin/ls","ls","-l",NULL);
char *arg[] = {"ls", "-a", NULL}
execv( "/bin/ls",arg);
execvp( "ls",arg);
exec[l or v][p][e]
exec函数里的參数能够分成3个部分,运行文件部分,命令參数部分,环境变量部分.
比如我要运行1个命令 ls -l /home/gateman
运行文件部分就是 “/usr/bin/ls”
命令參赛部分就是 “ls”,"-l","/home/gateman",NULL 见到是以ls开头 每1个空格都必须分开成2个部分, 并且以NULL结尾的啊.
环境变量部分, 这是1个数组,最后的元素必须是NULL 比如 char * env[] = {“PATH=/home/gateman”, “USER=lei”, “STATUS=testing”, NULL};
e 參数必须带环境变量部分, 环境变零部分參数会成为运行exec函数期间的环境变量, 比較少用
l 命令參数部分必须以"," 相隔, 最后1个命令參数必须是NULL
v 命令參数部分必须是1个以NULL结尾的字符串指针数组的头部指针.比如char * pstr就是1个字符串的指针, char * pstr[] 就是数组了, 分别指向各个字符串.
p 运行文件部分能够不带路径, exec函数会在$PATH中找
/* ccallshell.c */
#include
#include
#include
int main()
{
FILE * fp; /* for popen() */
char buffer[1024] = {0};
int childpid;
int i;
/********system()**********/
system("ls -alF"); /* 直接执行shell命令 */
system("bash ./test.sh"); /* 执行shell脚本 */
/********popen(可以是shell命令或者shell脚本)***********/
fp = popen(/*"./test_popen.sh"*/"printenv", "r"); /* fp应该是test_popen.sh执行结果输出的文件句柄 */
while (fgets(buffer, sizeof(buffer), fp)) /* 逐行读取 */
{
printf("%s", buffer);
}
pclose(fp);
/*******exec****************/
/* 带p的执行指令是相对路径,否则是绝对路径 */
/* 带e的最后一个参数是环境变量 */
/* 带l的是可变参数,带v的是把参数保存到参数变量,再使用该参数(本质相同) */
/* 防止在不同的环境下,shell指令的绝对路径不同导致可移植性差,这里用自定义的命令echo_test */
/* execl */
if (fork() == 0)
{
//child process
if (execl("./echo_test","echo_test","executed by execl" ,NULL) <0 ) /* 绝对路径,可变参数,不带环境变量 */
{
perror("error on exec");
exit(0);
}
}
else
{
//parent process
wait(&childpid);
printf("execv done\n\n");
}
/* execlp */
if (fork() == 0)
{
//child process
if (execlp("echo_test","echo_test","executed by execlp" ,NULL) <0 ) /* 相对路径,可变参数,不带环境变量 */
{
perror("error on exec");
exit(0);
}
}
else
{
//parent process
wait(&childpid);
printf("execv done\n\n");
}
/* execle */
if (fork() == 0)
{
//child process
char * env[] = {"SHELL=/bin/bash", "USER=root", NULL};
if (execle("./echo_test","echo_test","executed by execle",NULL,env) <0) /* 绝对路径,可变参数,带环境变量 */
{
perror("error on exec");
exit(0);
}
}
else
{
//parent process
wait(&childpid);
printf("execle done\n\n");
}
/* execv */
if (fork() == 0)
{
//child process
char * execv_str[] = {"echo_test", "executed by execv",NULL};
if (execv("./echo_test",execv_str) <0 ) /* 绝对路径,参数变量,不带环境变量 */
{
perror("error on exec");
exit(0);
}
}
else
{
//parent process
wait(&childpid);
printf("execv done\n\n");
}
/* excevp */
if (fork() == 0)
{
//child process
char * execvp_str[] = {"echo_test", "executed by execvp",NULL};
if (execvp("echo_test",execvp_str) <0 ) /* 相对路径,参数变量,不带环境变量 */
{
perror("error on exec");
exit(0);
}
}
else
{
//parent process
wait(&childpid);
printf("execvp done\n\n");
}
/* exceve */
if (fork() == 0)
{
//child process
char * execve_str[] = {"echo_test", "executed by execvp",NULL};
char * env[] = {"SHELL=/bin/bash", "USER=root", NULL};
if (execve("./echo_test",execve_str,env) <0 ) /* 绝对路径,参数变量,带环境变量 */
{
perror("error on exec");
exit(0);
}
}
else
{
//parent process
wait(&childpid);
printf("execve done\n\n");
}
return 0;
}
shell脚本test.sh:
echo "this is calling bash file test!"
shell脚本test_popen.sh:
printenv
自定义echo_test:
/* echo_test.c */
#include
#include
#include
int main(int argc, char **arg)
{
if (argc < 2)
{
printf("usage:./echo_test teststring\n");
return 0;
}
printf("%s\n", arg[1]);
return 0;
}
Makefile:
APP_NAME = ccallshell
APP_OBJS = ccallshell.o
CC = gcc
INC = ./
CFLAG += -g
.PHONY : all
all : $(APP_NAME) echo_test
$(APP_NAME) : $(APP_OBJS)
$(CC) $(CFLAG) $(APP_OBJS) -o $(APP_NAME)
echo_test : echo_test.o
$(CC) $(CFLAG) $^ -o $@
%.o : %.c
$(CC) -c $(CFLAG) $^ -o $@
.PHONY : clean
clean :
rm -f *.o
rm -f $(APP_NAME) $(APP_OBJS) echo_test