
介绍一下Linux系统中的代码执行shell等命令的几种操作方式:
一、标准流管道popen
该函数的原型是FILE * popen(const char* command, const char *type);
command:使我们要执行的命令,即上述的运行命令,
type:有两种可能的取值,“r”(代表读取)或者“w"(代表写入)
popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh-c来执行参数command的指令,参数type可使用“r”读取 或者“w”写入,根据type的值,popen()会创建管道连接到子进程的标准输出设备或者标准输入设备,然后返回一个文件指针。随后进程就可以利用此文件指针来读取子进程的标准输出设备或者写入子进程的标准输入设备。
这个函数可以大大减少代码的编写量,但使用不太灵活,不能自己创建管道那么灵活,并且popen()必须使用标准的I/o函数进行操作,也不能使用read(),wirte()这种不带缓冲的I/O函数,必须使用pclose()来关闭管道流,该函数关闭标准I/O流,并等待命令执行结束
个人总结:记住,向这个流中写内容相当于写入该命令(或者是该程序)标准输入; 向这个流中读数据相当于读取该命令(或者是该程序)的标准输出
写例子:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
int main()
{
FILE *fp;
char *cmd="cat > aaa"; ///该命令新建一个文件,提示用户输入数据作为其内容
char buf[1000]="ni hao wo de peng you!\n"; ///待输入的参数
if((fp=popen(cmd,"w"))==NULL)
return 1;
fwrite(buf,sizeof(char),1000,fp); ///通过文件描述符写入文件数据
pclose(fp); ///关闭文件描述符
exit(0);
return 0;
}
读例子:
LD_S32 RunSysCmd2(LD_CS8 *pCmd, LD_S8 *pRslBuf, LD_S32 bufSz)
{
LD_S32 ret = LD_FAILURE;
FILE *pFd = popen(pCmd, "r");//创建一个流管道生成一个子进程用于执行命令
if(pFd)
{
memset(pRslBuf, 0, bufSz);
if(fread(pRslBuf, bufSz - 1, 1, pFd) >= 0)//从该命令中读取数据
{
ret = LD_SUCCESS;
}
pclose(pFd);//关闭流管道
}
return ret;
}
二、system
system("cat "Read pipe successfully!" > test1")
三、exec
在Linux中,并不存在exec()函数,exec指的是一组函数,一共有6个,分别是:
#include <unistd.h>
extern char **environ;
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[]);
int execve(const char *path, char *const argv[], char *const envp[]);
其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。
exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
exec和source都属于bash内部命令(builtins commands),在bash下输入man exec或man source可以查看所有的内部命令信息。
bash shell的命令分为两类:外部命令和内部命令。外部命令是通过系统调用或独立的程序实现的,如sed、awk等等。内部
命令是由特殊的文件格式(.def)所实现,如cd、history、exec等等。
在说明exe和source的区别之前,先说明一下fork的概念。
fork是linux的系统调用,用来创建子进程(child process)。子进程是父进程(parent process)的一个副本,从父进程那里
获得一定的资源分配以及继承父进程的环境。子进程与父进程唯一不同的地方在于pid(process id)。
环境变量(传给子进程的变量,遗传性是本地变量和环境变量的根本区别)只能单向从父进程传给子进程。不管子进程的环境
变量如何变化,都不会影响父进程的环境变量。
shell
script:
有两种方法执行shell
scripts,一种是新产生一个shell,然后执行相应的shell
scripts;一种是在当前shell下执行,不
再启用其他shell。
新产生一个shell然后再执行scripts的方法是在scripts文件开头加入以下语句
#!/bin/sh
一般的script文件(.sh)即是这种用法。这种方法先启用新的sub-shell(新的子进程),然后在其下执行命令。
另外一种方法就是上面说过的source命令,不再产生新的shell,而在当前shell下执行一切命令。
source:
source命令即点(.)命令。
在bash下输入man source,找到source命令解释处,可以看到解释”Read and execute commands from filename in the
current shell
environment and …”。从中可以知道,source命令是在当前进程中执行参数文件中的各个命令,而不是另起子
进程(或sub-shell)。
exec:
在bash下输入man exec,找到exec命令解释处,可以看到有”No new process is created.”这样的解释,这就是说exec命
令不产生新的子进程。那么exec与source的区别是什么呢?
exec命令在执行时会把当前的shell process关闭,然后换到后面的命令继续执行。
系统调用exec是以新的进程去代替原来的进程,但进程的PID保持不变。因此,可以这样认为,exec系统调用并没有创建新的
进程,只是替换了原来进程上下文的内容。原进程的代码段,数据段,堆栈段被新的进程所代替。
popen,system和exec区别:
1.system和popen都是执行了类似的运行流程,大致是fork->execl->return。但是我们看到system在执行期间调用进程会一直等待shell命令执行完成(waitpid等待子进程结束)才返回,但是popen无须等待shell命令执行完成就返回了。我们可以理解system为串行执行,在执行期间调用进程放弃了”控制权”,popen为并行执行。
2.popen中的子进程没人给它”收尸”了啊?是的,如果你没有在调用popen后调用pclose那么这个子进程就可能变成”僵尸”。
3.对于管道已经很清楚,而管道写可能用的地方比较少。而对于写可能更常用的是system函数:
system("cat "Read pipe successfully!"
> test1")
4.如果不需要使用到程序的I/O数据流,那么system是最方便的。
而且system函数是C89和C99中标准定义的,可以跨平台使用。而popen是Posix 标准函数,可能在某些平台无法使用(windows应该是可以的吧,没做过测试)。
5.可以看出,popen可以控制程序的输入或者输出,而system的功能明显要弱一点,比如无法将读取结果用于程序中。
6.如果上述两个函数还无法满足你的交互需求,那么可以考虑exec函数组了。
system是用shell来调用程序=fork+exec+waitpid,而exec是直接让你的程序代替原来的程序运行。
system 是在单独的进程中执行命令,完了还会回到你的程序中。而exec函数是直接在你的进程中执行新的程序,新的程序会把你的程序覆盖,除非调用出错,否则你再也回不到exec后面的代码,就是说你的程序就变成了exec调用的那个程序了。
最后说一下exit函数:
exit是一个库函数,exit(1)表示发生错误后退出程序, exit(0)表示正常退出。
对你的程序来说,没有区别。对使用你的程序的人或者程序来说,区别可就大了。
一般来说,exit 0 可以告知你的程序的使用者:你的程序是正常结束的。如果 exit 非 0 值,那么你的程序的使用者通常会认为你的程序产生了一个错误。
以 shell 为例,在 shell 中调用完你的程序之后,用 echo $? 命令就可以看到你的程序的 exit 值。在 shell 脚
本中,通常会根据上一个命令的 $? 值来进行一些流程控制。
同样的情形出现在 C 语言的 exec 系列函数中。
例:
当你 exit 0 的时候,在调用环境 echo $? 就返回0,也就是说调用环境就认为你的这个程序执行正确
当你 exit 1 的时候,一般是出错定义这个1,也可以是其他数字,很多系统程序这个错误编号是有约定的含义的。
但不为0 就表示程序运行出错。 调用环境就可以根据这个返回值判断 你这个程序运行是否ok。搜索如果你用 脚本 a 调用 脚本b
,要在a中判断b是否正常返回,就是根据 exit 0 or 1 来识别。执行完b后, 判断 $? 就是返回值