使用管道在同一进程上执行多个shell命令时读取死锁

如何解决使用管道在同一进程上执行多个shell命令时读取死锁

我正在制作一个需要在同一个 bash shell 实例中运行多个命令的 C++ 程序。 我需要这个,因为一些命令正在设置一个 bash 变量,该变量需要被后续的命令。

我使用 pipes 制作文件描述符,然后使用 readwrite 读取和写入,这些管道的另一端连接到使用fork

当命令不返回输出时会出现问题,例如设置 bash 变量。 在下面的代码中,读取将永远挂在命令编号 2 上。我一直在寻找大约几天,似乎没有办法检测命令何时完成运行而无需在某处关闭管道。我相信如果我关闭管道,我将无法重新打开它,这意味着我需要制作一个没有加载变量的新 bash shell。

此外,我无法确定哪些命令不会返回输出,因为此代码将从 Web 服务器获取它需要运行的命令,并希望避免将命令与“&&”连接起来以进行粒度错误报告。

#include <unistd.h>
#include <fcntl.h>
#include <cstdlib>
#include <string>
#include <iostream>

using namespace std;

int main(int argc,char *argv[])
{
    int inPipeFD[2];
    int outPipeFD[2];

    // Create a read and write pipe for communication with the child process
    pipe(inPipeFD);
    pipe(outPipeFD);

    // Set the read pipe to be blocking
    fcntl(inPipeFD[0],F_SETFL,fcntl(inPipeFD[0],F_GETFL) & ~O_NONBLOCK);
    fcntl(inPipeFD[1],fcntl(inPipeFD[1],F_GETFL) & ~O_NONBLOCK);

    // Create a child to run the job commands in
    int pid = fork();

    if(pid == 0) // Child
    {
        // Close STDIN and replace it with outPipeFD read end
        dup2(outPipeFD[0],STDIN_FILENO);

        // Close STDOUT and replace it with inPipe read end
        dup2(inPipeFD[1],STDOUT_FILENO);

        system("/bin/bash");
    }
    else // Parent
    {
        // Close the read end of the write pipe
        close(outPipeFD[0]);

        // Close the write end of the read pipe
        close(inPipeFD[1]);
    }

    // Command 1
    char buf[256];
    string command = "echo test\n";
    write(outPipeFD[1],command.c_str(),command.length());
    read(inPipeFD[0],buf,sizeof(buf));
    cout << buf << endl;

    // Command 2
    char buf2[256];
    command = "var=worked\n";
    write(outPipeFD[1],buf2,sizeof(buf2));
    cout << buf2 << endl;

    // Command 3
    char buf3[256];
    command = "echo $var\n";
    write(outPipeFD[1],buf3,sizeof(buf3));
    cout << buf3 << endl;
}

有没有办法在不关闭管道的情况下检测孩子的命令已经完成?

解决方法

一种解决方案是将 bash 设置为交互模式,以 system("/bin/bash -i"); 开头,并将提示设置为最后一个命令的退出代码。

首先,一个方便的函数,让读写更简单:

std::string command(int write_fd,int read_fd,std::string cmd) {
    write(write_fd,cmd.c_str(),cmd.size());
    cmd.resize(1024); // turn cmd into a buffer
    auto len = read(read_fd,cmd.data(),cmd.size());
    if(len == -1) len = 0;
    cmd.resize(static_cast<std::size_t>(len));
    return cmd;
}

然后在您的父进程中:

    sleep(1); // ugly way to make reasonably sure the child has started bash
    int& out = outPipeFD[1]; // for convenience
    int& in = inPipeFD[0];   // for convenience

    // first,set the prompt
    std::cout << command(out,in,"export PS1='$?\\n'\n") << '\n';

    // then all these will print something
    std::cout << command(out,"echo test\n") << '\n';
    std::cout << command(out,"var=worked\n") << '\n';
    std::cout << command(out,"echo $var\n") << '\n';

通过这种方式,您将始终可以阅读一些内容 - 您还可以使用它来验证命令是否正确执行。


如果您的 bash 需要一个处于 -i(交互)模式的真实终端,我们必须在没有它的情况下进行。思路:

  • 为每个发送的命令添加 echo $? + 一个分隔符
  • 将管道设置为非阻塞模式,以便能够捕获杂项。糟糕的情况,例如发送了命令 exit
  • 阅读直到找到分隔符或发生错误。

为了使分隔符难以猜测(为了不能轻易强制读取不同步),我将为每个命令生成一个新的分隔符。

这里有一个例子,展示了这些想法和内联注释的情况:

#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>

#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <random>
#include <sstream>
#include <utility>
#include <vector>

// a function to generate a random string to use as a delimiter
std::string generate_delimiter() {
    thread_local std::mt19937 prng(std::random_device{}());
    thread_local std::uniform_int_distribution dist('a','z');
    thread_local auto gen = [&]() { return dist(prng); };

    std::string delimiter(128,0);
    std::generate(delimiter.begin(),delimiter.end(),gen);

    return delimiter;
}

// custom exit codes for the command function
enum exit_status_t {
    ES_WRITE_FAILED = 256,ES_READ_FAILED,ES_EXIT_STATUS_NOT_FOUND
};

// a function executing a command and returning the output and exit code
std::pair<std::vector<std::string>,exit_status_t> command(int write_fd,std::string cmd) {
    constexpr size_t BufSize = 1024;

    // a string that is unlikely to show up in the output:
    const std::string delim = generate_delimiter() + "\n";

    cmd += "\necho -e $?\"\\n\"" + delim; // add echoing of status code
    auto len = write(write_fd,cmd.size()); // send the commands
    if(len <= 0) return {{},ES_WRITE_FAILED};           // couldn't write,return

    cmd.resize(0); // use cmd to collect all read data
    std::string buffer(BufSize,0);

    // a loop to extract all data until the delimiter is found
    fd_set read_set{};
    FD_SET(read_fd,&read_set);
    while(true) {
        // wait until something happens on the pipe
        select(read_fd + 1,&read_set,nullptr,nullptr);

        if((len = read(read_fd,buffer.data(),buffer.size())) <= 0) {
            // Failed reading - pipe probably closed on the other side.
            // Add a custom exit code and the delimiter and break out.
            cmd += "\n" + std::to_string(ES_READ_FAILED) + "\n" + delim;
            break;
        }

        // append what was read to cmd
        cmd.append(buffer.begin(),buffer.begin() + len);

        // break out of the loop if we got the delimiter
        if(cmd.size() >= delim.size() &&
           cmd.substr(cmd.size() - delim.size()) == delim)
        {
            break;
        }
    }

    cmd.resize(cmd.size() - delim.size()); // remove the delimiter

    // put what was read in an istringstream for parsing
    std::istringstream is(cmd);

    // extract line by line
    std::vector<std::string> output;
    while(std::getline(is,cmd)) {
        output.push_back(cmd);
    }

    // extract the exit code at the last line
    exit_status_t retval = ES_EXIT_STATUS_NOT_FOUND;
    if(not output.empty()) { // should never be empty but ...
        retval = static_cast<exit_status_t>(std::stoi(output.back(),nullptr));
        output.resize(output.size() - 1);
    }

    return {output,retval}; // return the pair
}

测试驱动程序:

int main() {
    int inPipeFD[2];
    int outPipeFD[2];

    // Create a read and write pipe for communication with the child process
    pipe(inPipeFD);
    pipe(outPipeFD);

    // Set the read pipe to be non-blocking
    fcntl(inPipeFD[0],F_SETFL,fcntl(inPipeFD[0],F_GETFL) | O_NONBLOCK);
    fcntl(inPipeFD[1],fcntl(inPipeFD[1],F_GETFL) | O_NONBLOCK);

    // Create a child to run the job commands in
    int pid = fork();

    if(pid == 0) // Child
    {
        // Close STDIN and replace it with outPipeFD read end
        dup2(outPipeFD[0],STDIN_FILENO);
        close(outPipeFD[0]); // not needed anymore

        // Close STDOUT and replace it with inPipe read end
        dup2(inPipeFD[1],STDOUT_FILENO);
        close(inPipeFD[1]); // not needed anymore

        // execl() is cleaner than system() since it replaces the process
        // completely. Use /bin/sh instead if you'd like.
        execl("/bin/bash","bash",nullptr);
        return 1; // to not run the parent code in case execl fails
    }
    // Parent

    // Close the read end of the write pipe
    close(outPipeFD[0]);

    // Close the write end of the read pipe
    close(inPipeFD[1]);

    sleep(1);
    int& out = outPipeFD[1]; // for convenience
    int& in = inPipeFD[0];   // for convenience

    // a list of commands,including an erroneous command(foobar) + exit
    for(std::string cmd : {"echo test","var=worked","echo $var","foobar","exit"}) 
    {
        std::cout << "EXECUTING COMMAND: " << cmd << '\n';
        auto [output,exit_status] = command(out,cmd);
        // print what was returned
        for(auto str : output) std::cout << str << '\n';
        std::cout << "(exit status=" << exit_status << ")\n";
    }
}

可能的输出:

EXECUTING COMMAND: echo test
test
(exit status=0)
EXECUTING COMMAND: var=worked
(exit status=0)
EXECUTING COMMAND: echo $var
worked
(exit status=0)
EXECUTING COMMAND: foobar
bash: line 7: foobar: command not found
(exit status=127)
EXECUTING COMMAND: exit

(exit status=257)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res