PStreams

PStreams

POSIX Process Control in C++

Introduction

PStreams allows you to run another program from your C++ application and to transfer data between the two programs similar to shell pipelines.

In the simplest case, a PStreams class is like a C++ wrapper for the POSIX.2 functions popen(3) and pclose(3), using C++ iostreams instead of C's stdio library.

The library provides class templates in the style of the standard iostreams that can be used with any ISO C++ compiler on a POSIX platform. The classes use a streambuf class that uses fork(2) and the exec(2) family of functions to create a new process and creates up to three pipes to write/read data to/from the process.

The advantages over the standard popen() function are:

The library is free software, released under the Boost Software License - Version 1.0.

To help improve PStreams see the SourceForge project page.

 

Status

The latest release is 1.0.4

PStreams is still evolving but is stable and functional, defining ipstream and opstream classes for ISO C++ compilers, providing a convenient C++ alternative to popen() and more.

Future plans include a select()like function to wait for data on either stdout or stderr without spinning. One day the process-control features will be separated from the streambuf class, so that pstreambuf is implemented using a pprocess member.

 

Documentation

The FAQ answers some general questions about PStreams.
PStreams uses Doxygen to automatically generate API documentation from the sources.
The docs for the latest release are available online.
Docs for previous releases can be generated from the sources.

 

Download

Latest release is 1.0.4 and all you really need is the pstream.h header.

Releases and source code details are on the download page.

 

Usage

Please refer to the doxygen-generated documentation.

Using the PStreams classes is similar to using a std::fstream, except that a shell command is given rather than a filename:


// print names of all header files in current directory
redi::ipstream in("ls ./*.h");
std::string str;
while (in >> str) {
    std::cout << str << std::endl;
}

The command argument is a pointer to a null-terminated string containing a shell command line. This command is passed to /bin/sh using the -c flag and the shell performs the usual word-splitting, expansions and I/O redirections.

Alternatively, the process can be started with a vector of arguments:


// remove some files, capturing any error messages
std::vector<std::string> argv;
std::vector<std::string> errors;
argv.push_back("rm");
argv.push_back("./foo.txt");
argv.push_back("./bar.html");
redi::ipstream in("rm", argv, pstreambuf::pstderr);
std::string errmsg;
while (std::getline(in, errmsg)) {
    errors.push_back(errmsg);
}

If this form of initialisation is used and the file argument doesn't contain a slash then the actions of the shell will be duplicated in searching for an executable in PATH. The shell will not interpret the other arguments, so wildcard expansion will not take place if this interface is used and redirections cannot be used.

If an rpstream was used in the example above it would be necessary to replace the while condition like so:


while (std::getline(in.err(), errmsg)) {
    errors.push_back(errmsg);
}

This form can also be used with the unrestricted pstream and ipstream classes, but it is not strictly necessary.

Here is a more complete example, showing how to use std::istream::readsome() to read without blocking:

const pstreams::pmode mode = pstreams::pstdout|pstreams::pstderr;
ipstream child("echo OUT1; sleep 1; echo ERR >&2; sleep 1; echo OUT2", mode);
char buf[1024];
std::streamsize n;
bool finished[2] = { false, false };
while (!finished[0] || !finished[1])
{
    if (!finished[0])
    {
        while ((n = child.err().readsome(buf, sizeof(buf))) > 0)
            std::cerr.write(buf, n);
        if (child.eof())
        {
            finished[0] = true;
            if (!finished[1])
                child.clear();
        }
    }

    if (!finished[1])
    {
        while ((n = child.out().readsome(buf, sizeof(buf))) > 0)
            std::cout.write(buf, n).flush();
        if (child.eof())
        {
            finished[1] = true;
            if (!finished[0])
                child.clear();
        }
    }
}

top

 

Credits

The inspiration for this project came from seeing a message on the libstdc++-v3 mailing list asking what had happened to the pfstream classes that came with libstdc++-v2. Because I'd used a similar C++ wrapper for popen() at work, and because I want to learn about standard iostreams, I decided to try to write a solution that would work with standard-conforming C++ libraries. (The pfstream classes were based on the old AT&T-style iostreams and relied on GCC-specific libraries.) PStreams is the result of that decision.

The following have helped in one way or another:
Angelika Langer & Klaus Kreft for the invaluable examples in their book Standard C++ IOStreams and Locales.
John Levon for comments and advice on the interface design.
Philippe Elie who wrote the ChildReader class for the OProfile project, which was a good starting point when replacing popen() with hand-rolled fork/exec code.
Per Bothner et al. for the pfstream classes in the old libg++.
SourceForge for hosting this site and the project.
Brett Williams for testing things and finding bugs I missed.

top