在使用linux系统时,我们经常会使用终端命令执行一些系统命令,比如查看文件夹目录文件“ls -lha”,查看磁盘情况“df -h”,这些命令我们经常使用,那么问题来了,这种效果能使用程序模拟出来吗,手撸一个终端程序怎么样!😏
本节主要接收popen函数的使用。
👉 popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令。参数type可使用“r”代表读取,“w”代表写入。依照此type值,popen()会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中。
👉 type参数只能是读或者写中的一种,得到的返回值(标准I/O流)也具有和type相应的只读或只写类型。如果type是"r"则文件指针连接到command的标准输出;如果type是"w"则文件指针连接到command的标准输入。
command参数是一个指向以NULL结束的shell命令字符串的指针。这行命令将被传到bin/sh并使用-c标志,shell将执行这个命令。
popen()的返回值是个标准I/O流,必须由pclose来终止。前面提到这个流是单向的(只能用于读或写)。向这个流写内容相当于写入该命令的标准输入,命令的标准输出和调用popen()的进程相同;与之相反的,从流中读数据相当于读取命令的标准输出,命令的标准输入和调用popen()的进程相同。
使用时需要引入头文件:
#include <stdio.h>
👉 popen函数还创建一个管道用于父子进程间通信。父进程要么从管道读信息,要么向管道写信息,至于是读还是写取决于父进程调用popen时传递的参数。
定于如下:
FILE * popen( const char * command,const char * type);
参数一:要执行的命令;
参数二:操作类型,读(r)/写(w);
返回值:如果调用fork()或pipe()失败,或者不能分配内存将返回NULL,否则返回标准I/O流。popen()没有为内存分配失败设置errno值。如果调用fork()或pipe()时出现错误,errno被设为相应的错误类型。如果type参数不合法,errno将返回EINVAL。
#include <stdio.h>
#define CMD_MAX_LENS 256*100
static int GetCmdResult(const char* strCmd, char* strResult, unsigned int nSize)
{
if(strCmd == NULL || strResult == NULL)
return -1;
if(strResult)
{
FILE *fd;
unsigned int ch,i;
if((fd = popen(strCmd, "r")) == NULL)
{
printf("ERROR: get_system(%s)", strCmd);
return -1;
}
for(i=0; i<nSize;i++)
{
if((ch = fgetc(fd)) != EOF)
{
strResult[i] = (char)ch;
}
else
{
break;
}
}
pclose(fd);
}
return 0;
}
int main(int argc, char *argv[])
{
char strCmdResult[CMD_MAX_LENS+1] = {0};
int ret = GetCmdResult("df -h", strCmdResult, CMD_MAX_LENS);
if(ret == 0)
{
printf("cmd response:\n%s\n", strCmdResult);
}
return 0;
}
执行make,然后运行程序,结果打印为👇:
$ ./main.out
cmd response:
文件系统 容量 已用 可用 已用% 挂载点
udev 16G 0 16G 0% /dev
tmpfs 3.2G 17M 3.2G 1% /run
/dev/sdb4 226G 53G 162G 25% /
tmpfs 16G 4.0K 16G 1% /dev/shm
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 16G 0 16G 0% /sys/fs/cgroup
/dev/sdb2 457M 249M 175M 59% /boot
/dev/sdb1 487M 5.3M 481M 2% /boot/efi
/dev/loop10 163M 163M 0 100% /snap/gnome-3-28-1804/145
/dev/loop11 55M 55M 0 100% /snap/snap-store/558
/dev/loop12 51M 51M 0 100% /snap/snap-store/547
/dev/loop15 66M 66M 0 100% /snap/gtk-common-themes/1519
/dev/sda1 2.7T 1.8T 400G 60% /home
/dev/loop2 249M 249M 0 100% /snap/gnome-3-38-2004/99
/dev/loop17 44M 44M 0 100% /snap/snapd/14978
/dev/loop1 62M 62M 0 100% /snap/core20/1361
none 16G 164K 16G 1% /run/qemu
👉 (1)popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。这个管道必须由pclose()函数关闭,而不是fclose()函数。pclose()函数关闭标准I/O流,等待命令执行结束,然后返回shell的终止状态。如果shell不能被执行,则pclose()返回的终止状态与shell已执行exit一样。
(2)在编写具SUID/SGID权限的程序时请尽量避免使用popen(),popen()会继承环境变量,通过环境变量可能会造成系统安全的问题。
👉 本节主要对popen函数进行了讲解,对函数的定义,使用方式和调用关系做了整理,有不对的地方希望读友能够指出。