进程管道:popen函数实例

基础知识 

可能最简单的在两个程序之间传递数据的方法就是使用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
}

 小结

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FTL没有映射,跟发工资没有钱有什么区别

大家好&#xff0c;我是五月。 前言 FTL&#xff08;Flash Translation Layer&#xff09;&#xff0c;即闪存转换层&#xff0c;是各种存储设备的核心算法&#xff0c;作用是将Host传下来的逻辑地址转换成物理地址&#xff0c;也就是映射。 地址映射是FTL最原始最基本的功能…

苹果手机之间如何互传照片?批量传输操作指南

很多时候&#xff0c;我们用手机拍摄了好看的照片或者收藏了一些有趣的图片&#xff0c;想要分享给朋友&#xff0c;却不知道苹果手机之间如何互传照片&#xff1f;在分享大量照片的时候不清楚如何批量操作&#xff1f;别担心&#xff0c;下面小编就来分享一下苹果手机照片传输…

海思3559万能平台搭建:SPI输出h264码流

前言 面对各种各样的客户需求&#xff0c;spi接口也是一种传码流的形式&#xff0c;spi同步422可以保证抗干扰能力强的同时传输距离也很长&#xff0c;本文会介绍海思平台spi作为主机的发送功能以及发送码流的处理方式 1. 管脚复用&#xff1a; 首先需要配置的肯定是管脚复用&…

Linux进程间通信【共享内存】

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; Linux学习之旅 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 文章目录 &#x1f307;前言&#x1f3d9;️正文1、什么是共享内存&#xff1f;2、共享内存的相关知识2.1、共享内存的数据结构…

load_dataset加载huggingface数据集失败

1. 一般的加载方式 from datasets import load_dataset dataset_dict load_dataset(cmrc2018)这种加载方式可能会显示因为连接问题导致失败&#xff0c;此时可以在hugging face里面找到对应的页面下载下来 然后改一下代码&#xff1a; from datasets import load_dataset d…

springmvc整合thymeleaf

概述 Thymeleaf提供了一组Spring集成&#xff0c;使您可以将其用作Spring MVC应用程序中JSP的全功能替代品。 这些集成将使您能够&#xff1a; Controller像使用JSP一样&#xff0c;将Spring MVC 对象中的映射方法转发到Thymeleaf管理的模板。在模板中使用Spring表达式语言&…

罗马不是一天建成的,那为什么建了那么多罗马?

这一个罗马&#xff0c;那一个罗马&#xff0c;东一个罗马&#xff0c;西一个罗马&#xff0c;世界历史的大半部分都在跟罗马打交道。更要命的是四大文明古国还没有古代罗马。 存在感这么强&#xff0c;还不是四大文明古国&#xff0c;名字还难记&#xff0c;公元前居然就有共…

【C++】在线编译器推荐,让你随时随地编写代码

▒ 目录 ▒ &#x1f6eb; 问题描述环境 1️⃣ 支持调试网站Repl.itOnlineGDB 2️⃣ 不支持调试网站Wandboxjson.cnjdoodletutorialspointcppshellideonecoliruonline-ide 3️⃣ 性能分析网站Quick C BenchmarkCompare C Builds 4️⃣ 其它C Insights&#xff08;学习模板、C11…

Hightopo 使用心得(3)- 吸附与锚点

吸附与锚点是 HT for Web 中两个比较重要的概念。这两个概念在执行交互和动画时会经常被用到。 吸附&#xff0c;顾名思义&#xff0c;是一个节点吸附到另一个节点上。就像船底的贝类一样&#xff0c;通过吸附到船身&#xff0c;在船移动的时候自己也会跟着移动&#xff1b;而…

pandas---缺失值的处理

1. 处理缺失值 判断数据中是否包含NaN&#xff1a; pd.isnull(df)&#xff1b;pd.notnull(df) 存在缺失值nan: 删除存在缺失值的:dropna(axisrows) 不会修改原数据&#xff0c;需要接受返回值&#xff1b; 替换缺失值:fillna(value, inplaceTrue) value:替换成的值&#…

JavaScript数学对象-数字进制转换

关注“大前端私房菜”微信公众号&#xff0c;输入暗号【面试宝典】即可免费领取107页前端面试题。 什么是进制 进制就是达到指定位置时候进一位 常见的进制 十进制: 0 1 2 3 4 5 6 7 8 9 10 11 12 ... 99 100 101 二进制: 0 1 10 11 100 101 110 111 1000 八进制: 0 1 2 3 4 …

走进人工智能|GANs AI时代下的前卫艺术

前言&#xff1a; GANs的作用是以生成模型的形式学习数据分布&#xff0c;从而产生逼真的样本数据&#xff0c;可以应用于图像合成、风格转换、视频生成等领域。 文章目录 序言背景适用领域技术支持应用领域程序员如何学总结 序言 GANs&#xff08;生成对抗网络&#xff09;是…

ASEMI代理台湾光宝LTV-3120光耦合器中文资料

编辑-Z LTV-3120是一种高性能光耦&#xff0c;由于其可靠性、效率和多功能性&#xff0c;在各种应用中都很受欢迎。本文将全面了解LTV-3120其功能、应用以及它如何改进您的电子设计。 什么是光电耦合器&#xff1f; 光耦&#xff0c;也称为光隔离器&#xff0c;是一种利用光在…

Mediapipe实时3D目标检测和跟踪(自动驾驶实现)

&#x1f680; 导语 3D目标检测是根据物体的形状、位置和方向来识别和定位物体的任务。在2D目标检测中&#xff0c;被检测到的物体仅表示为矩形边界框。3D目标检测任务通过预测物体周围的包围框&#xff0c;可以获取物体的三维位置信息。 3D目标检测在各行各业都有广泛的应用。…

Flink 系列二 Flink 状态化流处理概述

本篇作为Flink系列的第二篇&#xff0c;第一篇是环境准备&#xff0c;需要的同学可以看&#xff1a;https://blog.csdn.net/lly576403061/article/details/130358449?spm1001.2014.3001.5501。希望可以通过系统的学习巩固该方面的知识&#xff0c;丰富自己的技能树。废话不多说…

jmeter模拟多用户并发

目录 前言&#xff1a; 一、100个真实的用户 二、100个用户同时登录 前言&#xff1a; JMeter可以轻松地模拟多用户并发&#xff0c;从而测试Web应用程序的性能和稳定性。 一、100个真实的用户 1、一个账号模拟100虚拟用户同时登录和100账号同时登录 区别 &#xff08;…

运维圣经:Webshell应急响应指南

目录 Webshell简介 Webshell检测手段 Webshell应急响应指南 一. Webshell排查 二. 确定入侵时间 三. Web日志分析 四. 漏洞分析 五. 漏洞复现 六. 清除Webshell并修复漏洞 七. Webshell防御方法 Webshell简介 Webshell通常指以JSP、ASP、 PHP等网页脚本文件形式存在…

Webstorm 加载vue项目时,特别卡顿,完美解决。觉得有用加好友打赏

觉得有用加好友打赏&#xff1a;QQ&#xff1a;854138497 上图cpu直接干满。 根据上图提示&#xff0c;直接 disable hints&#xff0c;或者到下图的settings里面设置。 Code vision取消后&#xff0c;webstorm 明显就不卡了。记得重启webstorm。 还有一种方式&#xff0c;根…

组合模式(十二)

请相信自己&#xff0c;请再次相信自己&#xff0c;请一定要相信自己 上一章简单介绍了装饰者模式(十一), 如果没有看过, 请观看上一章 一. 组合模式 引用 菜鸟教程里面的 组合 模式介绍: https://www.runoob.com/design-pattern/composite-pattern.html 组合模式&#xff0…

2. CompletableFuture

2.1 Future接口理论知识复习 Future接口&#xff08;FutureTask实现类&#xff09;定义了操作异步任务执行一些方法&#xff0c;如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务执行是否完毕等。 举例&#xff1a;比如主线程让一个子线程去执行任…