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:
popen()
vary
between systems. Some systems use bidirectional pipes, allowing reading
and writing on the same stream, but this is not supported everywhere.
Because PStreams doesn't use popen()
but re-implements it
at a lower level, bidirectional I/O is available on all systems.stderr
. Input PStreams can read
from the process' stderr
as well as stdout
.popen()
the PStreams classes can open a process
specified by a filename and a vector of arguments, similar to the
execv()
function.pclose()
can only close the stream and wait for the
process to terminate, the pstreambuf
class can also send signals
to the controlled process and test if it has terminated.The library is free software, released under the Boost Software License - Version 1.0.
To help improve PStreams see the SourceForge project page.
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.
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.
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.
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(); } } }
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.