我们在C语言里面有时候需要执行一些shell命令,或者通过shell命令获取一些返回的数据。
无需返回执行结果 system/exec
如果执行命令不要返回,那最常用的就是直接使用system
如
sysytem("reboot")
可以使用exec家族的函数,失败返回-1
#include <unistd.h>
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[]);
其实system的实现方式就是调用的execl函数
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("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127);
}
else
{
while (waitpid(pid, &status, 0) < 0)
{
if (errno != EINTR)
{
status = -1;
break;
}
}
}
return(status);
}
需要返回执行结果-popen
有时候我们需要返回数据信息,如执行ls -l
,这时就不能使用system
需要使用popen来实现了,popen总是和pclose一起出现被使用的。
popen() 创建一个管道,通过fork或者invoke一个子进程,然后执行command。
返回值在标准IO流中,由于是在管道之中,因此数据流是单向的,command只能产生stdout或者读取stdin,因此type只有两个值:‘w’或‘r’。
r表示command从管道中读取数据流,而w表示command的stdout输出到管道中。command无法同时读取和输出。popen返回该FIFO数据流的指针。
如下:
一般还要过滤下&符号
#define CMD_LEN 128
#define BUF_LEN 1400
void execute_cmd(const char* cmd, char* buf, int buf_len)
{
int status = -1;
FILE* fp;
char* p;
char pwd[CMD_LEN] = "";
char unpadding[CMD_LEN];
char fgets_buf[512];
int len;
int end;
if (buf == NULL || buf_len <= 0)
{
return;
}
len = strlen(cmd);
end = (int)cmd[len - 1];
/* unpadding */
memset(unpadding, 0, sizeof(unpadding));
if (end < len) {
memcpy(unpadding, cmd, len - end);
} else {
memcpy(unpadding, cmd, len);
}
if ((p = strchr(unpadding, '\n')) != NULL) {
strncpy(pwd, unpadding, p - unpadding);
++p;
}
else
exit(0);
printf("execute cmd = %s\n", p);
if ('&' == p[strlen(p) - 1]) {
status = system(p);
sprintf(buf, "%d", WEXITSTATUS(status));
} else {
fp = popen(p, "r");
if (fp != NULL)
{
while (fgets(fgets_buf, sizeof(fgets_buf), fstream))
{
if (len < sizeof(buf))
{
len += snprintf(buf+len, sizeof(buf)-len, "%s", buff);
}
}
pclose(fp);
}
}
}
需要返回执行结果-匿名管道pipe
使用管道来获取执行shell命令返回的信息,一般流程如下
- 1.创建进程,创建匿名管道
- 2.子进程使用dup函数复制描述符将shell命令行标准输出绑定到管道的写端
- 3.父进程从管道的读端读取数据
pipe函数
- 所需头文件:#include<unistd.h>
- 函数原型:int pipe(int fd[2]);
- 返回值:成功返回0,出错返回-1
dup函数
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main()
{
int fpipe[2] = {0};
pid_t fpid;
char massage[1000] = {0};
memset(massage, 0, 20);
if (pipe(fpipe) < 0)
{
printf("Create pipe error!\n");
}
fpid = fork();
if (fpid == 0)
{
close(fpipe[0]);
dup2(fpipe[1],STDOUT_FILENO);
system("ls");
}
else if (fpid > 0)
{
wait(NULL);
printf("this is father,recieve:");
fflush(stdout);
close(fpipe[1]);
read(fpipe[0], massage, 1000);
printf("%s\n",massage);
}
else
{
printf("create fork error!\n");
}
return 0;
}
另一种思路-有名管道fifo
不管是sysytem还是popen的使用,内部其实都会fork一个进程,这其实是很耗系统资源的,现在公司实现了一个方法,不过我一直也没理解为什么可以不耗资源呢。
实现的形式是这样的
一个单独的vshd.sh脚本,一开机就后台运行,然后创建fifo,一直在等待接收数据。
C语言里面每次要执行命令时,就把命令发给vshd.sh监听的管道,再创建一个新的管道用来等待接收vshd.sh将执行完的命令返回给C语言。
这样每次要执行命令时都不调用system/popen,而是通过管道发送给vshd.sh后台脚本来执行。
参考资料
https://blog.csdn.net/qq_27664167/article/details/82194391