进程管道:popen函数实例

news2024/11/26 18:32:37

基础知识 

可能最简单的在两个程序之间传递数据的方法就是使用popen和pclose函数了。它们的原型如下所示: 

        #include <stdio.h>

       FILE *popen(const char *command, const char *type);

       int pclose(FILE *stream);

1.popen函数 

popen函数允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者通过它接收数据。command字符串是要运行的程序名和相应的参数。open_mode必须是"r"或者"w"。

如果open_mode是"r",被调用程序的输出就可以被调用程序使用,调用程序利用popen函数返回的FILE*文件流指针,就可以通过常用的stdio库函数(如fread)来读取被调用程序的输出。如果open_mode是"w",调用程序就可以用fwrite调用向被调用程序发送数据,而被调用程序可以在自己的标准输入上读取这些数据。被调用的程序通常不会意识到自己正在从另一个进程读取数据,它只是在标准输入流上读取数据,然后做出相应的操作。 

每个popen调用都必须指定"r"或"w",在popen函数的标准实现中不支持任何其他选项。这意味着我们不能调用另一个程序并同时对它进行读写操作。popen函数在失败时返回一个空指针。如果想通过管道实现双向通信,最普通的解决方法是使用两个管道,每个管道负责一个方向的数据流。 

2.pclose函数 

用popen启动的进程结束时,我们可以用pclose函数关闭与之关联的文件流。pclose调用只在popen启动的进程结束后才返回。如果调用pclose时它仍在运行,pclose调用将等待该进程的结束。

pclose调用的返回值通常是它所关闭的文件流所在进程的退出码。如果调用进程在调用pclose之前执行了一个wait语句,被调用进程的退出状态就会丢失,因为被调用进程已结束。此时,pclose将返回-1并设置errno为ECHILD。 

实验 读取外部程序的输出

现在来看一个简单的popen和pclose示例程序popen1.c。我们将在程序中用popen访问uname命令给出的信息。命令uname -a的作用是打印系统信息,包括计算机型号、操作系统名称、版本和发行号,以及计算机的网络名。

完成程序的初始化工作后,打开一个连接到uname命令的管道,把管道设置为可读方式并让read_fp指向该命令的输出。最后,关闭read_fp指向的管道。

测试代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define DEBUG_INFO(format, ...) printf("%s - %d - %s :: "format"\n",__FILE__,__LINE__,__func__ ,##__VA_ARGS__)

void test_01(void){
    FILE *read_fp;
    char buf[BUFSIZ + 1];
    memset(buf, 0, sizeof(buf));
    DEBUG_INFO("BUFSIZ = %d",BUFSIZ);
    read_fp = popen("uname -a","r");
    if(read_fp == NULL){
        perror("popen:");
        return;
    }
    int len = fread(buf,1,BUFSIZ,read_fp);
    if(len <= 0){
        perror("fread:");
        pclose(read_fp);
        return;
    }
    DEBUG_INFO("len = %d,buf = %s",len,buf);
    pclose(read_fp);
}

int main(int argc, char**argv){
    test_01();
    DEBUG_INFO("hello world");
    return 0;
}

CMakeLists.txt文件

cmake_minimum_required(VERSION 3.8)
project(myapp)

# add_compile_options("-std=c++11")

add_executable(popen popen.c)

编译并执行

mkdir _build_ -p
cmake -S ./ -B _build_
make -C _build_
./_build_/popen

输出结果:

/big/work/ipc/popen.c - 12 - test_01 :: BUFSIZ = 8192
/big/work/ipc/popen.c - 24 - test_01 :: len = 89,buf = Linux ubuntu 4.19.260 #1 SMP Thu Sep 29 14:19:07 CST 2022 x86_64 x86_64 x86_64 GNU/Linux

/big/work/ipc/popen.c - 30 - main :: hello world

实验解析

这个程序用popen调用启动带有-a选项的uname命令。然后用返回的文件流读取最多BUFSIZ个字符(这个常量是在stdio.h中用#define语句定义的)的数据,并将它们打印出来显示在屏幕上。因为我们是在程序内部捕获uname命令的输出,所以可以处理它。

将输出送往popen 

看过捕获外部程序输出的例子后,我们再来看一个将输出发送到外部程序的示例程序popen2.c,它将数据通过管道送往另一个程序。我们在这里使用的是od(八进制输出)命令。

实验 将输出送往外部程序

 我们可以看到,下面这个程序popen2.c非常类似于前面的示例程序,唯一的不同是这个程序是将数据写入管道,而不是从管道中读取。

测试代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define DEBUG_INFO(format, ...) printf("%s - %d - %s :: "format"\n",__FILE__,__LINE__,__func__ ,##__VA_ARGS__)

void test_01(void){
    FILE *write_fp;
    char buf[BUFSIZ + 1];
    memset(buf, 0, sizeof(buf));
    DEBUG_INFO("BUFSIZ = %d",BUFSIZ);
    sprintf(buf,"0123456789ABCDEFGHIJKLMN\n");

    DEBUG_INFO("len = %d,buf = %s",strlen(buf),buf);    
    write_fp = popen("od -c","w");
    if(write_fp == NULL){
        perror("popen:");
        return;
    }
    int len = fwrite(buf,sizeof(char),strlen(buf),write_fp);
    if(len <= 0){
        perror("fwrite:");
        pclose(write_fp);
        return;
    }
    
    pclose(write_fp);
}

int main(int argc, char**argv){
    test_01();
    return 0;
}

 测试结果:

 实验解析

程序使用带有参数"w"的popen启动od -c命令,这样就可以向该命令发送数据了。然后它给od -c命令发送一个字符串,该命令接收并处理它,最后把处理结果打印到自己的标准输出上。在命令行上,我们可以用下面的命令得到同样的输出结果:

echo "0123456789ABCDEFGHIJKLMN" | od -c

 传递更多的数据

我们目前所使用的机制都只是将所有数据通过一次fread或fwrite调用来发送或接收。有时,我们可能希望能以块方式发送数据,或者我们根本就不知道输出数据的长度。为了避免定义一个非常大的缓冲区,我们可以用多个fread或fwrite调用来将数据分为几部分处理。下面这个程序popen3.c通过管道读取所有数据。

实验 通过管道读取大量数据 

在这个程序中,我们从被调用的进程ps ax中读取数据。该进程输出的数据有多少事先无法知道,所以我们必须对管道进行多次读取。

测试代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define DEBUG_INFO(format, ...) printf("%s - %d - %s :: "format"\n",__FILE__,__LINE__,__func__ ,##__VA_ARGS__)

void test_01(void){
    FILE *read_fp;
    char buf[BUFSIZ + 1];
    memset(buf, 0, sizeof(buf));
    DEBUG_INFO("BUFSIZ = %d",BUFSIZ);
    read_fp = popen("ps ax","r");
    if(read_fp == NULL){
        perror("popen:");
        return;
    }
    int len = fread(buf,1,BUFSIZ,read_fp);
    while(len > 0){
        buf[len - 1] = '\0';
        DEBUG_INFO("len = %d,buf = %s",len,buf);
        len = fread(buf,1,BUFSIZ,read_fp);
    }
    
    pclose(read_fp);
}

int main(int argc, char**argv){
    test_01();
    DEBUG_INFO("hello world");
    return 0;
}

 输出结果:

 实验解析

这个程序调用popen函数时使用了"r"参数,这与popen1.c程序的做法一样。这次,它连续从文件流中读取数据,直到没有数据可读为止。注意,虽然ps命令的执行要花费一些时间,但Linux会安排好进程间的调度,让两个程序在可以运行时继续运行。如果读进程popen3没有数据可读,它将被挂起直到有数据到达。如果写进程ps产生的输出超过了可用缓冲区的长度,它也会被挂起直到读进程读取了一些数据。

在本例中,你可能不会看到Reading:-信息的第二次出现。如果BUFSIZ的值超过了ps命令输出的长度,这种情况就会发生。一些(最新的)Linux系统将BUFSIZ设置为8192或更大的数字。为了测试程序在读取多个输出数据块时能够正常工作,你可以尝试每次读取少于BUFSIZ个字符(比如BUFSIZE/10个字符)。 

 如何实现popen

请求popen调用运行一个程序时,它首先启动shell,即系统中的sh命令,然后将command字符串作为一个参数传递给它。这有两个效果,一个好,一个不太好。

 在Linux(以及所有的类UNIX系统)中,所有的参数扩展都是由shell来完成的。所以,在启动程序之前先启动shell来分析命令字符串,就可以使各种shell扩展(如*.c所指的是哪些文件)在程序启动之前就全部完成。这个功能非常有用,它允许我们通过popen启动非常复杂的shell命令。而其他一些创建进程的函数(如execl)调用起来就复杂得多,因为调用进程必须自己去完成shell扩展。

使用shell的一个不太好的影响是,针对每个popen调用,不仅要启动一个被请求的程序,还要启动一个shell,即每个popen调用将多启动两个进程。从节省系统资源的角度来看,popen函数的调用成本略高,而且对目标命令的调用比正常方式要慢一些。 

我们用程序popen4.c来演示popen函数的行为。这个程序对所有popen示例程序的源文件的总行数进行统计,方法是用cat命令显示文件的内容并将输出通过管道传递给命令wc -l,由后者统计总行数。如果是在命令行上完成这一任务,我们可以使用如下命令:

cat popen*.c | wc -l

事实上,输入命令wc -l popen*.c更简单而且更有效率,但我们是为了通过这个例子来演示popen函数的工作原理。 

 实验popen启动shell

这个程序使用上面给出的命令,但是通过popen来读取命令输出的结果:

测试代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define DEBUG_INFO(format, ...) printf("%s - %d - %s :: "format"\n",__FILE__,__LINE__,__func__ ,##__VA_ARGS__)

void test_01(void){
    FILE *read_fp;
    char buf[BUFSIZ + 1];
    memset(buf, 0, sizeof(buf));
    DEBUG_INFO("BUFSIZ = %d",BUFSIZ);
    read_fp = popen("cat popen*.c | wc -l","r");
    if(read_fp == NULL){
        perror("popen:");
        return;
    }
    int len = fread(buf,1,BUFSIZ,read_fp);
    while(len > 0){
        buf[len - 1] = '\0';
        DEBUG_INFO("len = %d,buf = %s",len,buf);
        len = fread(buf,1,BUFSIZ,read_fp);
    }
    
    pclose(read_fp);
}

int main(int argc, char**argv){
    test_01();
    DEBUG_INFO("hello world");
    return 0;
}

执行结果:

 实验解析

这个程序显示,shell在启动后将popen*.c扩展为一个文件列表,列表中的文件名都以popen开头,以.c结尾,shell还处理了管道符(|)并将cat命令的输出传递给wc命令。我们在一个popen调用中启动了shell、cat程序和wc程序,并进行了一次输出重定向。而调用这些命令的程序只看到最终的输出结果。

CMakeLists.txt、编译脚本和sftp.json

 CMakeLists.txt

cmake_minimum_required(VERSION 3.8)
project(myapp)

# add_compile_options("-std=c++11")

add_executable(popen popen.c)
add_executable(popen2 popen2.c)
add_executable(popen3 popen3.c)
add_executable(popen4 popen4.c)

 编译脚本

rm -rf _build_
mkdir _build_ -p
cmake -S ./ -B _build_
make -C _build_
# ./_build_/popen
# ./_build_/popen2
# ./_build_/popen3
./_build_/popen4


 sftp.json

{
    "name": "My Server",
    "host": "192.168.31.138",
    "protocol": "sftp",
    "port": 22,
    "username": "lkmao",
    "password": "lkmao",
    "remotePath": "/big/work/ipc",
    "uploadOnSave": true,
    "useTempFile": false,
    "openSsh": false
}

 小结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/643490.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

因为Json,controller方法单参数 导致脑袋短路

对于单参数方法&#xff0c; 一直喜欢用parameter方式。今天不知道为啥&#xff0c;就想用Json方式&#xff0c;然后无法直接传递。各种自我怀疑&#xff0c;然后尝试。 突然醒悟过来&#xff0c;Json方式是key/value模式&#xff0c;单参数String类型&#xff0c;没有key。必…

TreeMap源码

介绍 如果我们希望Map可以保持key的大小顺序时&#xff0c;就需要利用TreeMap。底层使用了红黑树&#xff0c;左子树总小于root&#xff0c;右子树总大于root&#xff0c;具有很好的平衡性,操作速度达到log(n)。 TreeMap 相比于HashMap多实现了了NavigableMap接口&#xff08…

5. SpringCloudAlibab 集成 gateway

一、什么是 Spring Cloud Gateway 1、网关简介 网关作为流量的入口&#xff0c;常用的功能包括路由转发&#xff0c;权限校验&#xff0c;限流等等。 SpringCloud Gateway是 Spring Cloud 官方推出的第二代网关框架&#xff0c;定位取代 Netflix Zuul。相对Zuul来说&#xf…

【多线程】原子引用ABA问题

目录 一、代码示例二、执行结果截图三、说明四、AtomicStampedReference使用4.1 代码示例4.2 截图 一、代码示例 package com.learning.atomic;import lombok.extern.slf4j.Slf4j; import java.util.concurrent.atomic.AtomicReference; /*** Author wangyouhui* Description …

2023年软考-高级信息系统项目管理工程师考试大纲

高级信息系统项目管理工程师考试大纲 2023年软考高级信息系统项目管理工程师考试大纲已于2023年5月出版。您可以在 中国计算机技术职业资格网 上找到更多关于考试的信息 。 信息系统项目管理师是对从事信息系统项目管理工作的专业技术人员基本理论和实践能力的综合考核,该专业…

新手如何挑选一款合适的功率放大器?

ATA系列功率放大器是&#xff08;AB&#xff09;类功放&#xff0c;相比于甲类功率放大器&#xff0c;它小信号输入时效率更高&#xff0c;随着输出功率的增大&#xff0c;效率也增高&#xff0c;它的效率比以及保真度而言&#xff0c;都优于A类和B类功放。 因为具有这些优势&a…

助你更好的理解 Python 字典

助你更好的理解 Python 字典 字典是Python中的常用数据类型之一&#xff0c;可将数据存储在键/值对中&#xff0c;同 Java 中的 Map 相似。 1、什么是字典理解&#xff1f; 字典理解是创建字典的一种优雅简洁的方法。 字典理解优化 使用字典理解优化函数。 示例&#xff…

使用 FFmpeg 开发的那些事

勇敢就是&#xff0c;在你还没开始的时候就知道自己注定会输&#xff0c;但依然义无反顾地去做&#xff0c;并且不管发生什么都坚持到底。一个人很少能赢&#xff0c;但也总会有赢的时候。《杀死一只知更鸟》 欢迎大家加入广州城市社区&#xff1a;https://devpress.csdn.net/g…

java面经

String, StringBuffer, StringBuilder区别 第一点是可变性。String不可变&#xff0c;String Buffer和StringBuider可变。这是因为String被final修饰&#xff0c;每次操作都生成新的对象。StringBuffer和StringBuilder的父类AbstractStringBuilder没有被final修饰。 第二点是…

MATLAB 之 非线性方程数值求解、最优化问题求解和常微分方程初值问题的数值求解

这里写目录标题 一、非线性方程数值求解1. 单变量非线性方程求解2. 非线性方程组的求解 二、最优化问题求解1. 无约束最优化问题求解2. 有约束最优化问题求解3. 线性规划问题求解 三、常微分方程初值问题的数值求解1. 龙格—库塔法简介2. 龙格—库塔法的实现 一、非线性方程数值…

Day975.如何使用JWT结构化令牌 -OAuth 2.0

如何使用JWT结构化令牌 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于如何使用JWT结构化令牌的内容。 OAuth 2.0 规范并没有约束访问令牌内容的生成规则&#xff0c;只要符合唯一性、不连续性、不可猜性就够了。这就意味着&#xff0c;可以灵活选择令牌的形式&…

将递归函数转成非递归函数的通用方法

看到过一道非常不错的面试题&#xff1a;不支持递归的程序语言如何实现递归程序&#xff1f; 之所以说这道题好&#xff0c;是因为&#xff1a; 首先&#xff0c;它不是纯粹考概念和死记硬背&#xff0c;求职者在回答问题之前需要进行一定的思考&#xff1b; 其次&#xff0c…

vim粘贴出现多余的#

vim粘贴yaml格式时&#xff0c;出现多余的#&#xff0c;格式错误 解决&#xff1a;设置paste :set paste 然后再粘贴即可

final不可变性

一、什么是不可变性&#xff08;Immutable&#xff09; 如果对象在被创建后&#xff0c;状态就不能被修改&#xff0c;那么它就是不可变的这个对象不能被修改指&#xff1a; 对象指向(引用)不可变字段不可变成员变量不可变 案列演示&#xff1a; person对象&#xff0c;age和…

我的C++学习笔记

声明&#xff1a; 写本篇博客的目的是为了整理自己在找工作时学习的C相关知识点&#xff0c;博客整体内容会分为两种风格&#xff0c;第一章基础部分是以常见C面试问题解答的形式呈现&#xff1b;其余部分是知识点层层递进的方式展现&#xff0c;比较系统。其中&#xff0c;在第…

Avalon 学习系列(三)—— 数据和指令同步

Avalon 有很多个指令&#xff0c;通过这些指令可以对 DOM 进行一些事件操作、或者样式修改。 ms-duplex Avalon 实现数据与视图的同步的方式是用 ms-duplex 将元素跟数据绑定在一起&#xff0c;如果有其中一个的值改变另一个值也将改变。 ms-duplex 是 avalon 的双向绑定属性…

OpenCV(C++)创建图片绘制图形(矩形、圆、文字、线段等等)

一、OpenCV介绍 OpenCV 是基于开源许可证的跨平台计算机视觉库,提供了一组丰富、广泛的图像处理和计算机视觉算法。OpenCV 支持多种编程语言,包括 C++、Python、Java 等,可以运行在 Linux、Windows、Mac OS 等平台上。 OpenCV 能够在图像上绘制各种几何形状、文本和曲线,…

学习ESP32笔记

学习ESP32笔记 1.platform IO插件的下载&#xff08;提前安装好python&#xff0c;不然在中间的一部分会一直报错&#xff09; VS Code下载platform IO时&#xff0c;开加速器&#xff08;VPN&#xff09;&#xff0c;并且关闭防火墙 这一步比较慢&#xff0c;大概等十来分钟…

Kendo UI for jQuery---02.开始---01.使用 Kendo UI for jQuery 的第一步

使用 Kendo UI for jQuery 的第一步 欢迎来到 Kendo UI for jQuery 入门的第一步指南&#xff01; 本指南演示如何通过添加所需资源和初始化 Kendo UI 网格来开始使用套件。 该过程借鉴了以下里程碑&#xff1a; 1.下载控件 2.添加所需的 JavaScript 和 CSS 文件 3.将网格绑…

如何使用postman做接口测试

常用的接口测试工具主要有以下几种&#xff1a; Postman: 简单方便的接口调试工具&#xff0c;便于分享和协作。具有接口调试&#xff0c;接口集管理&#xff0c;环境配置&#xff0c;参数化&#xff0c;断言&#xff0c;批量执行&#xff0c;录制接口&#xff0c;Mock Server…