# cpp-subprocess gives C++ the Popen and pipeline calls Python had

> cpp-subprocess is a single header C++ library that models the Python 2.7 subprocess module, with Popen, check_output, communicate, pipes, output redirection and an environment map, and it is MIT licensed. It suits a C++ project that has to launch child processes without hand rolling fork and exec, and it supports macOS and Linux with Windows support described as limited.

**arun11299/cpp-subprocess** — Subprocessing with modern C++

- Repository: https://github.com/arun11299/cpp-subprocess
- Stars: 572 · Forks: 109
- Language: C++
- License: NOASSERTION
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/arun11299-cpp-subprocess

## One header file, and an API that reads like Python

cpp-subprocess describes itself as subprocessing with modern C++, and the design goals are stated as a translation rather than a redesign. The only goal was to develop something that is as close as python2.7 subprocess module in dealing with processes, because the author could not find anything similar done for C++.

The three goals are worth quoting in spirit. The intuitive interface, using modern C++ features so that API usage is as simple as in Python in most cases and even better in some cases. Correctness, on the grounds that dealing with processes is not a simple job with many low level details that need to be taken care of. And error handling, currently achieved via the use of exceptions, with the important requirement that no errors are ignored silently.

The last of those three is the one the author is candid about: he says he is working towards achieving this goal, but most of the error conditions have been taken care of.

The integration story is unusually simple for a library. There is just a single source file, subprocess.hpp, present at the top directory of the repository, and all you need to do is add the include and pick a namespace alias.

```cpp
#include "cpp-subprocess/subprocess.hpp"

using namespace subprocess;
// OR
// namespace sp = subprocess; 
// Or give any other alias you like.
```

The include path is written as cpp-subprocess/subprocess.hpp, which means the repository is expected to sit inside a directory of that name, and the C++11 switch has to be added, with -std=c++11 in g++ and clang.

## check_output for the easy case, Popen for everything else

The examples start with the case you probably want most of the time, which is running a command and getting its output back.

```cpp
auto obuf = check_output({"ls", "-l"});
std::cout << "Data : " << obuf.buf.data() << std::endl;
std::cout << "Data len: " << obuf.length << std::endl;
```

One call, and the buffer and its length are handed back. That is the shape of the Python one-liner, and it covers a large share of real use: run this, capture that, check it worked.

When you need the process itself rather than just its output, the detailed form uses Popen with an output pipe.

```cpp
auto p = Popen({"ls", "-l"}, output{PIPE});
auto obuf = p.communicate().first;
std::cout << "Data : " << obuf.buf.data() << std::endl;
std::cout << "Data len: " << obuf.length << std::endl;
```

Popen returns an object you hold, and communicate returns a pair, with the first element being the captured output. The naming is deliberately faithful: Popen, communicate, PIPE.

That faithfulness is the library's whole value proposition for a C++ developer who already thinks in Python, and it is also its main limitation, discussed later.

## Sending input, redirecting output and reading it back

The second example shows the write side, and it offers two ways to do the same thing.

Redirecting a message input to `cat` command to a file.

```cpp
auto p = Popen({"cat", "-"}, input{PIPE}, output{"cat_fredirect.txt"});
auto msg = "through stdin to stdout";
p.send(msg, strlen(msg));
p.wait();
```

Here the child is `cat -`, its stdin is a pipe and its stdout is redirected to a file, so the data you send is written to the file. The alternative form passes the message straight into communicate.

```cpp
auto p = Popen({"cat", "-"}, input{PIPE}, output{"cat_fredirect.txt"});
auto msg = "through stdin to stdout";
p.communicate(msg, strlen(msg))
```

And the third form reads redirected output back from stdout, using grep as the filter and feeding it several lines of input.

```cpp
auto p = Popen({"grep", "f"}, output{PIPE}, input{PIPE});
auto msg = "one\ntwo\nthree\nfour\nfive\n";
p.send(msg, strlen(msg));
auto res = p.communicate();

std::cout << res.first.buf.data() << std::endl;
```

The three forms together cover the ways a C++ program normally needs a child process: write to it, read from it, or put its output in a file. That is the low level detail the library claims to take care of, and the reason a C++ project otherwise ends up with a hand written fork and exec wrapper per platform.

## Pipes between processes, and a one-liner shortcut

The piping example is where the library is furthest from what C++ offers out of the box, because it wires the output of one child into the input of another the way a shell pipeline does.

```cpp
auto cat = Popen({"cat", "../subprocess.hpp"}, output{PIPE});
auto grep = Popen({"grep", "template"}, input{cat.output()}, output{PIPE});
auto cut = Popen({"cut", "-d,", "-f", "1"}, input{grep.output()}, output{PIPE});
auto res = cut.communicate().first;
std::cout << res.buf.data() << std::endl;
```

Three children, each reading the previous one's output pipe. The `output()` accessor is what makes it work: it hands back the read end of a child's pipe so it can be used as the input of the next process.

For simple commands there is a shortcut that does the same thing in one call.

```cpp
auto res = pipeline("cat ../subprocess.hpp", "grep Args", "grep template");
std::cout << res.buf.data() << std::endl;
```

The commands provided to the `pipeline` function is in the order that would have appeared in your regular unix command.

That is a genuine convenience, and it is also where a C++ library starts to look like a shell. The `pipeline` helper takes command strings rather than argument vectors, which means the arguments go through a shell-style parse, and that is a different security and quoting model from the explicit Popen form above.

## Environment variables as a map, and where the header stops

The last documented feature is passing environment variables, which is the case where C++ usually has the least to offer.

For example, if a shell script requires some new environment variables to be defined, you can provide it with the below easy to use syntax.

```cpp
int st= Popen("./env_script.sh", environment{{
                                   {"NEW_ENV1", "VALUE-1"},
                                   {"NEW_ENV2", "VALUE-2"},
                                   {"NEW_ENV3", "VALUE-3"}
             }}).wait();
assert (st == 0);
```

The environment option takes an initialiser list of key and value pairs, and the call returns the exit status, which you can assert on.

The repository layout is small and worth noting. There is a top-level CMakeLists.txt, a cmake/ directory, a cpp-subprocess/ directory that holds the actual source, and a test/ directory. The documentation also points at a blog post, templated-thoughts.blogspot.in, for a longer walk through of the same library.

For CMake consumers there is a FetchContent route.

```cmake
include(FetchContent)
FetchContent_Declare(
    cpp-subprocess
    GIT_REPOSITORY https://github.com/arun11299/cpp-subprocess.git
    GIT_TAG        v2.2
)
FetchContent_MakeAvailable(cpp-subprocess)

target_link_libraries(<your_target> PRIVATE cpp-subprocess::subprocess)
```

The supported compilers are g++ 4.8 and above on Linux, Clang 3.4 and later on macOS, and MSVC 2015 and above on Windows.

## Where this library is the wrong choice

There are clear cases against, and the most important one is that the API is a translation rather than a native design.

The library's stated goal is to be as close as python2.7 subprocess module as possible. That is useful if you are porting Python code, and it is a liability in two ways. First, it anchors the library to an API that is roughly a decade old, with the interface decisions of that era, including exception-based error handling which the author himself describes as a work in progress. Second, the `pipeline` helper takes shell-like command strings, so the quoting and escaping rules are the shell's, while the Popen form takes argument vectors and the shell's are not involved. Two ways of doing the same thing with different escaping behaviour is one more thing to get wrong.

The second case against is scope. If your program only needs to run a command and read its output, the C++ standard library and one call to std::system will do, with no dependency, no header, and no platform branches. cpp-subprocess earns its place when you need interactive pipes, input redirection, or per-child environment control, and the price is a dependency that compiles into your build.

The third is Windows. The documentation says the library supports MAC OS and Linux, and that support for Windows is limited at this time, with a request to report specific use cases that fail so they can be fixed as they are reported. The release history shows a v2.0 tagged as Windows support in 2020, and the listed releases since then are maintenance releases, v2.1 in April 2025 and v2.5 in September 2025. So a cross platform team on Windows should expect the platform to be the weaker half of the support story.

## Conclusion

cpp-subprocess fits a C++ codebase that has to launch child processes and has been copying Python idioms or writing its own fork and exec wrapper, since the API is deliberately a translation of the Python 2.7 subprocess module rather than a new design. It does not fit a project that needs more than a synchronous call to run a program and collect its output, since the standard library plus std::system can do that with far less machinery. Before adopting it, check that your platform is one of the two the library supports properly, macOS and Linux, and that you can live with the exception-based error handling the documentation describes, because the library states that error conditions are currently handled by exceptions and that the author is still working towards that goal.

## FAQ

### How do I use cpp-subprocess in a C++ project?

Add the single header with #include "cpp-subprocess/subprocess.hpp", pick a namespace alias such as using namespace subprocess, and make sure your compiler is given C++11 support with -std=c++11 in g++ and clang.

### Which platforms does cpp-subprocess support?

The library supports MAC OS and Linux. Support for Windows is limited at this time, and the documentation asks that specific failing use cases are reported so they can be fixed.

### How do I pipe the output of one child process into another with cpp-subprocess?

Create each child with output{PIPE} and pass the previous child's output() as the input of the next one, for example Popen({"grep", "template"}, input{cat.output()}, output{PIPE}). For simple commands there is also a pipeline helper that takes the commands in the order they would appear in a unix command.

### How does cpp-subprocess handle errors?

Error handling is currently achieved through the use of exceptions, with the stated goal that no errors are ignored silently. The author says he is still working towards that goal, but that most of the error conditions have been taken care of.

## Sources

- [arun11299/cpp-subprocess on GitHub](https://github.com/arun11299/cpp-subprocess)
- [Issues](https://github.com/arun11299/cpp-subprocess/issues)
- [README](https://github.com/arun11299/cpp-subprocess/blob/master/README.md)
- [Releases](https://github.com/arun11299/cpp-subprocess/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/arun11299-cpp-subprocess
