Introduction
In many applications, there is a need to execute external shell commands from within a program. This can be useful for integrating system utilities, performing tasks that are easier to implement as scripts, or leveraging existing command-line tools. In this tutorial, we’ll explore how to execute shell commands in C++ and capture their output using both POSIX-compliant systems (like Linux) and Windows.
Executing Commands on POSIX Systems
POSIX-compliant systems provide the popen()
function to execute a command in a subshell, allowing you to read or write data through standard input/output streams. This is particularly useful when you need to capture the output of a command executed by your program.
Using popen()
Here’s how you can use popen()
to execute a command and capture its output:
#include <cstdio>
#include <memory>
#include <stdexcept>
#include <string>
std::string exec(const char* cmd) {
std::array<char, 128> buffer;
std::string result;
// Use unique_ptr to manage the FILE pointer returned by popen
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
if (!pipe) {
throw std::runtime_error("popen() failed!");
}
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
result += buffer.data();
}
return result;
}
int main() {
try {
std::string output = exec("./some_command");
std::cout << "Command Output: \n" << output;
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what();
}
return 0;
}
Handling Errors and Compatibility
When using popen()
with modern C++ compilers, you may encounter issues due to changes in how attributes are handled. A workaround involves explicitly handling the return value of pclose()
:
std::unique_ptr<FILE, void(*)(FILE*)> pipe(
popen(cmd, "r"),
[](FILE* f) {
std::ignore = pclose(f); // Ignore the return value from pclose()
}
);
Advanced Command Execution with pstreams
For more sophisticated scenarios, such as capturing both standard output and error streams simultaneously, consider using libraries like pstreams
. This library simplifies handling multiple streams:
#include <pstream.h>
#include <iostream>
#include <string>
int main() {
redi::ipstream proc("./some_command", redi::pstreams::pstdout | redi::pstreams::pstderr);
std::string line;
// Read and print standard output
while (std::getline(proc.out(), line)) {
std::cout << "stdout: " << line << '\n';
}
if (proc.eof() && proc.fail()) {
proc.clear(); // Reset state to continue reading stderr
}
// Read and print standard error
while (std::getline(proc.err(), line)) {
std::cout << "stderr: " << line << '\n';
}
return 0;
}
Executing Commands on Windows
On Windows, using popen()
directly opens a console window which might be undesirable. Instead, you can use the Win32 API for more control over process execution and output capture.
Using Win32 API
Here’s how to execute a command and capture its standard output without displaying a console window:
#include <windows.h>
#include <atlstr.h>
CStringA ExecCmd(const wchar_t* cmd) {
CStringA strResult;
HANDLE hPipeRead, hPipeWrite;
SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES), TRUE, nullptr };
if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0)) {
return strResult;
}
STARTUPINFOW si = { sizeof(si) };
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.hStdOutput = hPipeWrite;
si.wShowWindow = SW_HIDE;
PROCESS_INFORMATION pi = {};
BOOL fSuccess = CreateProcessW(nullptr, (LPWSTR)cmd, nullptr, nullptr, TRUE, CREATE_NEW_CONSOLE, nullptr, nullptr, &si, &pi);
if (!fSuccess) {
CloseHandle(hPipeWrite);
CloseHandle(hPipeRead);
return strResult;
}
bool bProcessEnded = false;
for (; !bProcessEnded; ) {
bProcessEnded = WaitForSingleObject(pi.hProcess, 50) == WAIT_OBJECT_0;
char buffer[1024];
DWORD dwRead = 0;
if (!PeekNamedPipe(hPipeRead, nullptr, 0, nullptr, &dwRead, nullptr)) break;
if (dwRead == 0) break;
if (!ReadFile(hPipeRead, buffer, sizeof(buffer) - 1, &dwRead, nullptr) || !dwRead) break;
buffer[dwRead] = '\0';
strResult += buffer;
}
CloseHandle(hPipeWrite);
CloseHandle(hPipeRead);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return strResult;
}
int main() {
CStringA output = ExecCmd(L"some_command.exe");
std::cout << "Command Output: \n" << (const char*)output.GetString();
return 0;
}
Conclusion
Executing shell commands and capturing their output is a powerful capability in C++. By utilizing popen()
on POSIX systems or the Win32 API on Windows, developers can integrate system utilities into their applications. Understanding how to manage input/output streams effectively allows for more robust and versatile software solutions.