我如何执行一个命令,并获得命令的输出在c++使用POSIX?

我正在寻找一种方法来获得命令的输出,当它从c++程序中运行时。我已经考虑过使用system()函数,但它只会执行一个命令。以下是我正在寻找的一个例子:

std::string result = system("./some_command");

我需要运行任意命令并获得其输出。我看了boost.org,但我没有找到任何东西可以给我我需要的。

641483 次浏览

两种可能的方法:

  1. 我不认为popen()是c++标准的一部分(从内存来看,它是POSIX的一部分),但是它在我使用过的每个UNIX上都可用(您似乎针对的是UNIX,因为您的命令是./some_command)。

  2. 如果没有popen(),您可以使用system("./some_command >/tmp/some_command.out");,然后使用正常的I/O函数来处理输出文件。

#include <cstdio>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <array>


std::string exec(const char* cmd) {
std::array<char, 128> buffer;
std::string result;
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;
}

Pre-C + + 11版:

#include <iostream>
#include <stdexcept>
#include <stdio.h>
#include <string>


std::string exec(const char* cmd) {
char buffer[128];
std::string result = "";
FILE* pipe = popen(cmd, "r");
if (!pipe) throw std::runtime_error("popen() failed!");
try {
while (fgets(buffer, sizeof buffer, pipe) != NULL) {
result += buffer;
}
} catch (...) {
pclose(pipe);
throw;
}
pclose(pipe);
return result;
}

对于Windows,用_popen_pclose替换popenpclose

我会用popen () (+ +瓦)

但有时你需要阅读和写作……

似乎没有人再用困难的方式做事了。

(假设是Unix/Linux/Mac环境,或者是带有POSIX兼容层的Windows…)

enum PIPE_FILE_DESCRIPTERS
{
READ_FD  = 0,
WRITE_FD = 1
};


enum CONSTANTS
{
BUFFER_SIZE = 100
};


int
main()
{
int       parentToChild[2];
int       childToParent[2];
pid_t     pid;
string    dataReadFromChild;
char      buffer[BUFFER_SIZE + 1];
ssize_t   readResult;
int       status;


ASSERT_IS(0, pipe(parentToChild));
ASSERT_IS(0, pipe(childToParent));


switch (pid = fork())
{
case -1:
FAIL("Fork failed");
exit(-1);


case 0: /* Child */
ASSERT_NOT(-1, dup2(parentToChild[READ_FD], STDIN_FILENO));
ASSERT_NOT(-1, dup2(childToParent[WRITE_FD], STDOUT_FILENO));
ASSERT_NOT(-1, dup2(childToParent[WRITE_FD], STDERR_FILENO));
ASSERT_IS(0, close(parentToChild [WRITE_FD]));
ASSERT_IS(0, close(childToParent [READ_FD]));


/*     file, arg0, arg1,  arg2 */
execlp("ls", "ls", "-al", "--color");


FAIL("This line should never be reached!!!");
exit(-1);


default: /* Parent */
cout << "Child " << pid << " process running..." << endl;


ASSERT_IS(0, close(parentToChild [READ_FD]));
ASSERT_IS(0, close(childToParent [WRITE_FD]));


while (true)
{
switch (readResult = read(childToParent[READ_FD],
buffer, BUFFER_SIZE))
{
case 0: /* End-of-File, or non-blocking read. */
cout << "End of file reached..."         << endl
<< "Data received was ("
<< dataReadFromChild.size() << "): " << endl
<< dataReadFromChild                << endl;


ASSERT_IS(pid, waitpid(pid, & status, 0));


cout << endl
<< "Child exit staus is:  " << WEXITSTATUS(status) << endl
<< endl;


exit(0);




case -1:
if ((errno == EINTR) || (errno == EAGAIN))
{
errno = 0;
break;
}
else
{
FAIL("read() failed");
exit(-1);
}


default:
dataReadFromChild . append(buffer, readResult);
break;
}
} /* while (true) */
} /* switch (pid = fork())*/
}

您还可能希望使用select()和非阻塞读取。

fd_set          readfds;
struct timeval  timeout;


timeout.tv_sec  = 0;    /* Seconds */
timeout.tv_usec = 1000; /* Microseconds */


FD_ZERO(&readfds);
FD_SET(childToParent[READ_FD], &readfds);


switch (select (1 + childToParent[READ_FD], &readfds, (fd_set*)NULL, (fd_set*)NULL, & timeout))
{
case 0: /* Timeout expired */
break;


case -1:
if ((errno == EINTR) || (errno == EAGAIN))
{
errno = 0;
break;
}
else
{
FAIL("Select() Failed");
exit(-1);
}


case 1:  /* We have input */
readResult = read(childToParent[READ_FD], buffer, BUFFER_SIZE);
// However you want to handle it...
break;


default:
FAIL("How did we see input on more than one file descriptor?");
exit(-1);
}

获得stdout和stderr(也写入stdin,这里没有显示)很容易与我的pstreams头,它定义iostream类的工作方式像popen:

#include <pstream.h>
#include <string>
#include <iostream>


int main()
{
// run a process and create a streambuf that reads its stdout and stderr
redi::ipstream proc("./some_command", redi::pstreams::pstdout | redi::pstreams::pstderr);
std::string line;
// read child's stdout
while (std::getline(proc.out(), line))
std::cout << "stdout: " << line << '\n';
// if reading stdout stopped at EOF then reset the state:
if (proc.eof() && proc.fail())
proc.clear();
// read child's stderr
while (std::getline(proc.err(), line))
std::cout << "stderr: " << line << '\n';
}

下面可能是一个可移植的解决方案。它遵循标准。

#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
#include <sstream>


std::string ssystem (const char *command) {
char tmpname [L_tmpnam];
std::tmpnam ( tmpname );
std::string scommand = command;
std::string cmd = scommand + " >> " + tmpname;
std::system(cmd.c_str());
std::ifstream file(tmpname, std::ios::in | std::ios::binary );
std::string result;
if (file) {
while (!file.eof()) result.push_back(file.get())
;
file.close();
}
remove(tmpname);
return result;
}


// For Cygwin


int main(int argc, char *argv[])
{
std::string bash = "FILETWO=/cygdrive/c/*\nfor f in $FILETWO\ndo\necho \"$f\"\ndone ";
std::string in;
std::string s = ssystem(bash.c_str());
std::istringstream iss(s);
std::string line;
while (std::getline(iss, line))
{
std::cout << "LINE-> " + line + "  length: " << line.length() << std::endl;
}
std::cin >> in;
return 0;
}

对于Windows, popen也可以工作,但它会打开一个控制台窗口-它会在你的UI应用程序上快速闪烁。如果你想成为一个专业人士,最好禁用这种“闪烁”(特别是如果最终用户可以取消它)。

下面是我自己的Windows版本:

(此代码部分重新组合了用代码项目和MSDN示例编写的思想。)

#include <windows.h>
#include <atlstr.h>
//
// Execute a command and get the results. (Only standard output)
//
CStringA ExecCmd(
const wchar_t* cmd              // [in] command to execute
)
{
CStringA strResult;
HANDLE hPipeRead, hPipeWrite;


SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES)};
saAttr.bInheritHandle = TRUE; // Pipe handles are inherited by child process.
saAttr.lpSecurityDescriptor = NULL;


// Create a pipe to get results from child's stdout.
if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0))
return strResult;


STARTUPINFOW si = {sizeof(STARTUPINFOW)};
si.dwFlags     = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.hStdOutput  = hPipeWrite;
si.hStdError   = hPipeWrite;
si.wShowWindow = SW_HIDE; // Prevents cmd window from flashing.
// Requires STARTF_USESHOWWINDOW in dwFlags.


PROCESS_INFORMATION pi = { 0 };


BOOL fSuccess = CreateProcessW(NULL, (LPWSTR)cmd, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
if (! fSuccess)
{
CloseHandle(hPipeWrite);
CloseHandle(hPipeRead);
return strResult;
}


bool bProcessEnded = false;
for (; !bProcessEnded ;)
{
// Give some timeslice (50 ms), so we won't waste 100% CPU.
bProcessEnded = WaitForSingleObject( pi.hProcess, 50) == WAIT_OBJECT_0;


// Even if process exited - we continue reading, if
// there is some data available over pipe.
for (;;)
{
char buf[1024];
DWORD dwRead = 0;
DWORD dwAvail = 0;


if (!::PeekNamedPipe(hPipeRead, NULL, 0, NULL, &dwAvail, NULL))
break;


if (!dwAvail) // No data available, return
break;


if (!::ReadFile(hPipeRead, buf, min(sizeof(buf) - 1, dwAvail), &dwRead, NULL) || !dwRead)
// Error, the child process might ended
break;


buf[dwRead] = 0;
strResult += buf;
}
} //for


CloseHandle(hPipeWrite);
CloseHandle(hPipeRead);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return strResult;
} //ExecCmd

我不明白为什么popen/pclose块代码:/MinGW中消失了。所以我使用CreateProcess()CreatePipe()来解决这个问题。

以下是对我有效的解决方案:

//C++11
#include <cstdio>
#include <iostream>
#include <windows.h>
#include <cstdint>
#include <deque>
#include <string>
#include <thread>


using namespace std;


int SystemCapture(
string         CmdLine,    //Command Line
string         CmdRunDir,  //set to '.' for current directory
string&        ListStdOut, //Return List of StdOut
string&        ListStdErr, //Return List of StdErr
uint32_t&      RetCode)    //Return Exit Code
{
int                  Success;
SECURITY_ATTRIBUTES  security_attributes;
HANDLE               stdout_rd = INVALID_HANDLE_VALUE;
HANDLE               stdout_wr = INVALID_HANDLE_VALUE;
HANDLE               stderr_rd = INVALID_HANDLE_VALUE;
HANDLE               stderr_wr = INVALID_HANDLE_VALUE;
PROCESS_INFORMATION  process_info;
STARTUPINFO          startup_info;
thread               stdout_thread;
thread               stderr_thread;


security_attributes.nLength              = sizeof(SECURITY_ATTRIBUTES);
security_attributes.bInheritHandle       = TRUE;
security_attributes.lpSecurityDescriptor = nullptr;


if (!CreatePipe(&stdout_rd, &stdout_wr, &security_attributes, 0) ||
!SetHandleInformation(stdout_rd, HANDLE_FLAG_INHERIT, 0)) {
return -1;
}


if (!CreatePipe(&stderr_rd, &stderr_wr, &security_attributes, 0) ||
!SetHandleInformation(stderr_rd, HANDLE_FLAG_INHERIT, 0)) {
if (stdout_rd != INVALID_HANDLE_VALUE) CloseHandle(stdout_rd);
if (stdout_wr != INVALID_HANDLE_VALUE) CloseHandle(stdout_wr);
return -2;
}


ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION));
ZeroMemory(&startup_info, sizeof(STARTUPINFO));


startup_info.cb         = sizeof(STARTUPINFO);
startup_info.hStdInput  = 0;
startup_info.hStdOutput = stdout_wr;
startup_info.hStdError  = stderr_wr;


if(stdout_rd || stderr_rd)
startup_info.dwFlags |= STARTF_USESTDHANDLES;


// Make a copy because CreateProcess needs to modify string buffer
char      CmdLineStr[MAX_PATH];
strncpy(CmdLineStr, CmdLine.c_str(), MAX_PATH);
CmdLineStr[MAX_PATH-1] = 0;


Success = CreateProcess(
nullptr,
CmdLineStr,
nullptr,
nullptr,
TRUE,
0,
nullptr,
CmdRunDir.c_str(),
&startup_info,
&process_info
);
CloseHandle(stdout_wr);
CloseHandle(stderr_wr);


if(!Success) {
CloseHandle(process_info.hProcess);
CloseHandle(process_info.hThread);
CloseHandle(stdout_rd);
CloseHandle(stderr_rd);
return -4;
}
else {
CloseHandle(process_info.hThread);
}


if(stdout_rd) {
stdout_thread=thread([&]() {
DWORD  n;
const size_t bufsize = 1000;
char         buffer [bufsize];
for(;;) {
n = 0;
int Success = ReadFile(
stdout_rd,
buffer,
(DWORD)bufsize,
&n,
nullptr
);
printf("STDERR: Success:%d n:%d\n", Success, (int)n);
if(!Success || n == 0)
break;
string s(buffer, n);
printf("STDOUT:(%s)\n", s.c_str());
ListStdOut += s;
}
printf("STDOUT:BREAK!\n");
});
}


if(stderr_rd) {
stderr_thread=thread([&]() {
DWORD        n;
const size_t bufsize = 1000;
char         buffer [bufsize];
for(;;) {
n = 0;
int Success = ReadFile(
stderr_rd,
buffer,
(DWORD)bufsize,
&n,
nullptr
);
printf("STDERR: Success:%d n:%d\n", Success, (int)n);
if(!Success || n == 0)
break;
string s(buffer, n);
printf("STDERR:(%s)\n", s.c_str());
ListStdErr += s;
}
printf("STDERR:BREAK!\n");
});
}


WaitForSingleObject(process_info.hProcess,    INFINITE);
if(!GetExitCodeProcess(process_info.hProcess, (DWORD*) &RetCode))
RetCode = -1;


CloseHandle(process_info.hProcess);


if(stdout_thread.joinable())
stdout_thread.join();


if(stderr_thread.joinable())
stderr_thread.join();


CloseHandle(stdout_rd);
CloseHandle(stderr_rd);


return 0;
}


int main()
{
int            rc;
uint32_t       RetCode;
string         ListStdOut;
string         ListStdErr;


cout << "STARTING.\n";


rc = SystemCapture(
"C:\\Windows\\System32\\ipconfig.exe",    //Command Line
".",                                     //CmdRunDir
ListStdOut,                              //Return List of StdOut
ListStdErr,                              //Return List of StdErr
RetCode                                  //Return Exit Code
);
if (rc < 0) {
cout << "ERROR: SystemCapture\n";
}


cout << "STDOUT:\n";
cout << ListStdOut;


cout << "STDERR:\n";
cout << ListStdErr;


cout << "Finished.\n";


cout << "Press Enter to Continue";
cin.ignore();


return 0;
}

假设是POSIX,捕获stdout的简单代码:

#include <sys/wait.h>
#include <unistd.h>
#include <string>
#include <vector>


std::string qx(const std::vector<std::string>& args) {
int stdout_fds[2];
pipe(stdout_fds);


int stderr_fds[2];
pipe(stderr_fds);


const pid_t pid = fork();
if (!pid) {
close(stdout_fds[0]);
dup2(stdout_fds[1], 1);
close(stdout_fds[1]);


close(stderr_fds[0]);
dup2(stderr_fds[1], 2);
close(stderr_fds[1]);


std::vector<char*> vc(args.size() + 1, 0);
for (size_t i = 0; i < args.size(); ++i) {
vc[i] = const_cast<char*>(args[i].c_str());
}


execvp(vc[0], &vc[0]);
exit(0);
}


close(stdout_fds[1]);


std::string out;
const int buf_size = 4096;
char buffer[buf_size];
do {
const ssize_t r = read(stdout_fds[0], buffer, buf_size);
if (r > 0) {
out.append(buffer, r);
}
} while (errno == EAGAIN || errno == EINTR);


close(stdout_fds[0]);


close(stderr_fds[1]);
close(stderr_fds[0]);


int r, status;
do {
r = waitpid(pid, &status, 0);
} while (r == -1 && errno == EINTR);


return out;
}

为获得更多功能,欢迎代码贡献:

https://github.com/ericcurtin/execxx

您可以在使用管道运行脚本后获得输出。当我们需要子进程的输出时,我们使用管道。

int my_func() {
char ch;
FILE *fpipe;
FILE *copy_fp;
FILE *tmp;
char *command = (char *)"/usr/bin/my_script my_arg";
copy_fp = fopen("/tmp/output_file_path", "w");
fpipe = (FILE *)popen(command, "r");
if (fpipe) {
while ((ch = fgetc(fpipe)) != EOF) {
fputc(ch, copy_fp);
}
}
else {
if (copy_fp) {
fprintf(copy_fp, "Sorry there was an error opening the file");
}
}
pclose(fpipe);
fclose(copy_fp);
return 0;
}

这就是你要运行的脚本。将它与脚本所接受的参数一起放在命令变量中(如果没有参数,就什么都没有)。以及您想要捕获脚本输出的文件,将其放在copy_fp中。

popen运行你的脚本并将输出放到fpipe中然后你可以将所有内容复制到输出文件中。

通过这种方式,您可以捕获子流程的输出。

另一个过程是您可以直接将>操作符只放在命令中。因此,如果我们在运行命令时将所有内容都放在一个文件中,则不需要复制任何内容。

在这种情况下,不需要使用管道。您可以只使用system,它将运行命令并将输出放在该文件中。

int my_func(){
char *command = (char *)"/usr/bin/my_script my_arg > /tmp/my_putput_file";
system(command);
printf("everything saved in my_output_file");
return 0;
}

您可以阅读YoLinux教程:Fork, Exec和过程控制 . 0以获得更多信息。

请注意,您可以通过将输出重定向到文件,然后读取它来获得输出

它在std::system的文档中显示

您可以通过调用WEXITSTATUS宏来接收退出代码。

    int status = std::system("ls -l >test.txt"); // execute the UNIX command "ls -l >test.txt"
std::cout << std::ifstream("test.txt").rdbuf();
std::cout << "Exit code: " << WEXITSTATUS(status) << std::endl;

瓦格的答案的c++流实现:

#include <istream>
#include <streambuf>
#include <cstdio>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <string>


class execbuf : public std::streambuf {
protected:
std::string output;
int_type underflow(int_type character) {
if (gptr() < egptr()) return traits_type::to_int_type(*gptr());
return traits_type::eof();
}
public:
execbuf(const char* command) {
std::array<char, 128> buffer;
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(command, "r"), pclose);
if (!pipe) {
throw std::runtime_error("popen() failed!");
}
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
this->output += buffer.data();
}
setg((char*)this->output.data(), (char*)this->output.data(), (char*)(this->output.data() + this->output.size()));
}
};


class exec : public std::istream {
protected:
execbuf buffer;
public:
exec(char* command) : std::istream(nullptr), buffer(command, fd) {
this->rdbuf(&buffer);
}
};

这段代码捕获通过stdout的所有输出。如果你只想捕获stderr,然后像这样传递你的命令:

sh -c '<your-command>' 2>&1 > /dev/null

如果你想同时捕获stdoutstderr,那么命令应该如下所示:

sh -c '<your-command>' 2>&1

命令类使用system("cmd >stdout 2比;Stderr"),为用户提供stdout和stderr,以及退出代码。

测试运行:

./a.out 'ls .'
exit code: 0
stdout: HelloWorld
HelloWorld.c
HelloWorld.cpp
HelloWorld.dSYM
a.out
gcc_container.bash
linuxsys
macsys
test.sh


stderr:


#include <iostream>
#include <fstream>
#include <sstream>
#include <unistd.h>
using namespace std;


class Command {
public:
Command() {
exit_code_ = -1;
}


int GetExitCode() { return exit_code_;}


string GetStdOutStr() {return stdout_str_;}


string GetStdErrStr() {return stderr_str_;}


int Run(const char* cmd) {
return Run(string(cmd));
}


/**
* @brief run a given command
*
* @param cmd: command string
* @return int: the exit code of running the command
*/
int Run(string cmd) {


// create temp files
char tmp_dir[] = "/tmp/stdir.XXXXXX";
mkdtemp(tmp_dir);
string stdout_file = string(tmp_dir) + "/stdout";
string stderr_file = string(tmp_dir) + "/stderr";


// execute the command "cmd > stdout_file 2> stderr_file"
string cli = cmd + " > " + stdout_file + " 2> " + stderr_file;
exit_code_ = system(cli.c_str());
exit_code_ = WEXITSTATUS(exit_code_);
stdout_str_ = File2Str(stdout_file);
stderr_str_ = File2Str(stderr_file);


// rid of the temp files
remove(stdout_file.c_str());
remove(stderr_file.c_str());
remove(tmp_dir);


return exit_code_;
}


private:
int exit_code_;
string stderr_str_;
string stdout_str_;


/**
* @brief read a file
*
* @param file_name: file path
* @return string the contents of the file.
*/
string File2Str(string file_name) {
ifstream file;
stringstream str_stream;


file.open(file_name);
if (file.is_open()) {
str_stream << file.rdbuf();
file.close();
}
return str_stream.str();
}
};


int main(int argc, const char* argv[]) {
Command command;


command.Run(argv[1]);
cout << "exit code: " << command.GetExitCode() << endl;
cout << "stdout: " << command.GetStdOutStr() << endl;
cout << "stderr: " << command.GetStdErrStr() << endl;
return  command.GetExitCode();
}