2

I'm trying to run an external shell command and read its output using the Boost libraries for C++ but it seems that either the command is not running or I just can't access the output. I'm using their documentation as example and wrote this:

#include <boost/process.hpp>
namespace bp = boost::process;

bool is_process_running(std::string p_name){
        string cmd = "ps aux 2>&1";
        bp::ipstream out;
        std::string line;
        bp::child c(cmd, bp::std_out > out);

        // the while is supposed to read each line of the output
        // but the execution doesn't enter here
        while(c.running() && std::getline(out, line) && !line.empty())
        {
            if(line.find(p_name) != std::string::npos)
            {
                return true;
            }
        }
        c.wait();
        return false;
}

The goal is to verify each line output of ps aux and search if a process is running. So, what could be the problem here? Or, can you provide a simple snippet for doing this?

Fnr
  • 2,096
  • 7
  • 41
  • 76
  • please be more specific on what is the output and what output you expect. How do you know that "the execution doesn't enter here" ? Did you already use a debugger? – 463035818_is_not_an_ai Sep 05 '18 at 14:44
  • yes, I'm debugging it. The point is, it is not entering the while loop and according to the example in the docs this is how I'd get the output from the `cmd` execution line by line – Fnr Sep 05 '18 at 14:46
  • Answer to [this question](https://stackoverflow.com/questions/478898/how-to-execute-a-command-and-get-output-of-command-within-c-using-posix) should solve your problem. You can execute for example `ps aux | grep PROC_NAME | grep -v "grep .* PROC_NAME"`. – pptaszni Sep 05 '18 at 14:50
  • 2
    Does boost documentation tell you it will launch a shell? If not, you cannot use shell syntax like `2>&1`. – n. m. could be an AI Sep 05 '18 at 18:50

2 Answers2

2

I've had this issue... I could only get process to work using boost::asio.

Here's the code, hopefully that will help. The code below handles all three of the child process's streams.

The only external is exename_, and tstring is a std::basic_string<TCHAR>.

void UBC::Run(
    const tstring& args,
    const std::string& input,
    std::string& output,
    std::string& error)
{
    using namespace boost;

    asio::io_service ios;

    std::vector<char> vOut(128 << 10);
    auto outBuffer{ asio::buffer(vOut) };
    process::async_pipe pipeOut(ios);

    std::function<void(const system::error_code & ec, std::size_t n)> onStdOut;
    onStdOut = [&](const system::error_code & ec, size_t n)
    {
        output.reserve(output.size() + n);
        output.insert(output.end(), vOut.begin(), vOut.begin() + n);
        if (!ec)
        {
            asio::async_read(pipeOut, outBuffer, onStdOut);
        }
    };

    std::vector<char> vErr(128 << 10);
    auto errBuffer{ asio::buffer(vErr) };
    process::async_pipe pipeErr(ios);
    std::function<void(const system::error_code & ec, std::size_t n)> onStdErr;
    onStdErr = [&](const system::error_code & ec, size_t n)
    {
        error.reserve(error.size() + n);
        error.insert(error.end(), vErr.begin(), vErr.begin() + n);
        if (!ec)
        {
            asio::async_read(pipeErr, errBuffer, onStdErr);
        }
    };

    auto inBuffer{ asio::buffer(input) };
    process::async_pipe pipeIn(ios);

    process::child c(
        tstring(exeName_) + _T(" ") + args, 
        process::std_out > pipeOut, 
        process::std_err > pipeErr, 
        process::std_in < pipeIn
    );


    asio::async_write(pipeIn, inBuffer, 
        [&](const system::error_code & ec, std::size_t n) 
        {
            pipeIn.async_close();
        });

    asio::async_read(pipeOut, outBuffer, onStdOut);
    asio::async_read(pipeErr, errBuffer, onStdErr);

    ios.run();
    c.wait();
}
Michaël Roy
  • 6,338
  • 1
  • 15
  • 19
  • This is in principle over complicating. It might be required when input/output are truly async and need to be intermixed - i.e. deadlock would occur if you kept synchronously reading when the process required input. The command in the example does not take any input (in fact one could `bp::std_in.close()` or `bp::std_in < bp::null`) – sehe Sep 05 '18 at 20:38
  • Also, `tstring(exeName_) + _T(" ") + args` will not work as advertised. Is that from real working code? That looks suspicuous. – sehe Sep 05 '18 at 20:39
  • That's how I got it to work, after turning around in circles for hours, like the OP. I did need all three streams. That was for an encryption utility. This is production code, not an example. – Michaël Roy Sep 05 '18 at 21:03
  • But there is no way that `bp::child` will accept the arguments separated by just a space there. What version of boost is that? And is it unmodified? (If that works it either is or used to be a weird bug) – sehe Sep 05 '18 at 21:04
  • @sehe: `tstring(exeName_° + T(" " ) + args` works as advertized, since exeName_ is a `const TCHAR*`. It would also work as `tstring{} + exeName_ + _T(" ") + args`, but not without the reference to `tstring`. – Michaël Roy Sep 05 '18 at 21:08
  • @sehe. It's using boost 1.62.0. And it does work in the field. bp::child accepts a simple command line. – Michaël Roy Sep 05 '18 at 21:09
  • This is based on another boosrt::process example, that is also buggy. The vOut buffer is empty in the example, which makes it fail. – Michaël Roy Sep 05 '18 at 21:14
  • I'm miffed. It's expressly documented to only accept a binary name (fully path-qualified even). – sehe Sep 05 '18 at 21:27
  • Oh wait up a second. Boost 1.62 **doesn't even have Boost Process**. You must have been using alpha versions (and I remember them being highly volatile). I wouldn't advertise solutions based on that version as "good practice". – sehe Sep 05 '18 at 21:27
  • It's 1.64. Project is a few months old, sorry. By the way, my post is about helping OP solve his issue. Not about gleaning a few poiints, dude. – Michaël Roy Sep 05 '18 at 22:03
  • (I'm not a dude. And I don't remember talking about points. Why bring that up?) I'm also here helping others/learning, which is why I think it's important to discuss correctness issues/bugs with answers, right. If it surprises me and contradicts documented behaviour, then a fair warning is in order. If the docs need to be fixed, I'm all for it. – sehe Sep 05 '18 at 23:16
  • I think I found the solution though. Apparently though the examples only mention `bp::system` here https://www.boost.org/doc/libs/1_65_1/doc/html/boost_process/design.html#boost_process.design.arg_cmd_style the same logic is also applied to the other API entry points (? just guessing here) like `bp::system` or `bp::child`. It would be really nice to have the documentation improved. – sehe Sep 05 '18 at 23:27
  • I had assumed that, since we can supply the arguments separately, they must be (except for `system()` which has that documented, and always had that behaviour even [in POSIX](http://man7.org/linux/man-pages/man3/system.3.html)). Indeed [this works](http://coliru.stacked-crooked.com/a/174422822f219c48) but [not with redirection](http://coliru.stacked-crooked.com/a/8ded2188a0d68152). So I wouldn't recommend it because apparently the parsing is really limited and breaks common shell constructs. – sehe Sep 05 '18 at 23:27
  • Bootom line is: the code above works, with boost 1.64. I haven't had any issues with it. And yes, the examples provided in the doc leave to be desired, since they don't work. – Michaël Roy Sep 05 '18 at 23:36
  • Yup. I learned another surprising thing about Boost Process again. Thanks. I'd love if the interface was more consistent (e.g. https://github.com/boostorg/process/issues/37). For completeness, here's a version modernized to Boost 1.67 and correctly handling shell redirects: http://coliru.stacked-crooked.com/a/4be23435f72b6021 – sehe Sep 06 '18 at 00:16
2

Simply use a shell (or use bp::system):

Live On Coliru

#include <boost/process.hpp>
namespace bp = boost::process;

bool is_process_running(std::string p_name){
    std::vector<std::string> args { "-c", "ps aux 2>&1" };
    bp::ipstream out;
    bp::child c(bp::search_path("sh"), args, bp::std_out > out);

    for (std::string line; c.running() && std::getline(out, line);) {
        if (line.find(p_name) != std::string::npos) {
            return true;
        }
    }
    c.wait();

    return false;
}

#include <iostream>
int main() {
    std::cout << "bogus: " << is_process_running("bogus") << "\n";
    std::cout << "a.out: " << is_process_running("a.out") << "\n";
}

Prints

bogus: 0
a.out: 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • But why? Why not just `..., bp::std_out > out, bp::std_err > out)`? – n. m. could be an AI Sep 06 '18 at 13:05
  • @n.m. Mmm. That might actually work. But I don't think that's the essence of the question. Also the "semantics" of that might be different from the shells redirect depending on how exactly it's implemented. I'd say the question as posed has merit for that reason, and your comment is a welcome addition. – sehe Sep 06 '18 at 15:09