系列文章目录


前言

在使用linux系统时,我们经常会使用终端命令执行一些系统命令,比如查看文件夹目录文件“ls -lha”,查看磁盘情况“df -h”,这些命令我们经常使用,那么问题来了,这种效果能使用程序模拟出来吗,手撸一个终端程序怎么样!😏

在这里插入图片描述

本节主要接收popen函数的使用。

一、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()的进程相同。

1.头文件

使用时需要引入头文件:

#include <stdio.h>

2.函数定义

👉 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。

3.使用示例

#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

4.注意事项

👉 (1)popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。这个管道必须由pclose()函数关闭,而不是fclose()函数。pclose()函数关闭标准I/O流,等待命令执行结束,然后返回shell的终止状态。如果shell不能被执行,则pclose()返回的终止状态与shell已执行exit一样。
(2)在编写具SUID/SGID权限的程序时请尽量避免使用popen(),popen()会继承环境变量,通过环境变量可能会造成系统安全的问题。

三、总结

👉 本节主要对popen函数进行了讲解,对函数的定义,使用方式和调用关系做了整理,有不对的地方希望读友能够指出。
本节示例的代码编译测试提供给大家popen用法示例,也希望大家能够验证一下。

Logo

更多推荐