I am trying to execute a command within C++ and get the stdout and stderror of that command separately.
I have tried used popen() however since it only captures stdout, I have had to redirect stderr to stdout. Is there a way to get stdout and stderr separately on Linux?
So far I have been using:
std::string runCommand(const char * command) {
std::array<char, 128> buffer;
std::string ret;
FILE* pipe = popen(std::string(command).append(" 2>&1").c_str(), "r");
while (fgets(buffer.data(), 128, pipe) != NULL)
ret += buffer.data();
pclose(pipe);
return ret;
}
Am I on the right track? Should I continue to use popen or would the pipe/fork/exec method be better for this sort of problem?
Related
The application I'm working on needs to execute commands. Commands can be console commands or 'GUI applications' (like notepad).
I need to get the return code in both cases, and in the case of console commands I also need to catch the output from stdin and stderr.
In order to implement this feature, I based my code on the stack overflow question 'How to execute a command and get output of command within C++ using POSIX?'.
My code:
int ExecuteCmdEx(const char* cmd, std::string &result)
{
char buffer[128];
int retCode = -1; // -1 if error ocurs.
std::string command(cmd);
command.append(" 2>&1"); // also redirect stderr to stdout
result = "";
FILE* pipe = _popen(command.c_str(), "r");
if (pipe != NULL) {
try {
while (!feof(pipe)) {
if (fgets(buffer, 128, pipe) != NULL)
result += buffer;
}
}
catch (...) {
retCode = _pclose(pipe);
throw;
}
retCode = _pclose(pipe);
}
return retCode;
}
It works perfectly with console applications, but in the case of 'GUI applications' it doesn't work as expected...
With 'GUI applications', code stops on while (!feof(pipe)) expecting to get something from pipe.
I understand that 'GUI applications' like notepad don't finish until someone interacts with them (user closes the app, kills the process, etc.),
but when I launch console applications from Windows Console, prompt comes back immediately.
I would like to obtain the same behavior from 'GUI applications'...
One possible solution would be to add the isGui variable indicating when the code should read from the pipe, but I rejected this option, as I don't want to indicate if it is a 'GUI application' or not.
Well you don't have to indicate isGui yourself but detect it by checking the subsystem of the executable (windows/console) prior to executing the command, and in case of windows skip waiting on the redirected pipes.
For example, using SHGetFileInfo with the SHGFI_EXETYPE flag:
bool isGuiApplication(const std::string& command)
{
auto it = command.find_first_of(" \t");
const std::string& executable = (it == std::string::npos ? command : command.substr(0, it));
DWORD_PTR exetype = SHGetFileInfo(executable.c_str(), 0, nullptr, 0, SHGFI_EXETYPE);
if (!exetype) {
cerr << "Executable check failed\n";
}
return ((uintptr_t)exetype & 0xffff0000);
}
Then later in the code...
if (isGuiApplication(command)) {
cout << "GUI application\n";
system(command.c_str()); // don't wait on stdin
}
else {
cout << "Console application\n";
. . .
// _popen and stuff
}
I've got a piece of code that executes a process and retrieves the result.
namespace {
FILE* really_popen(const char* cmd, const char* mode) {
#ifdef _MSC_VER
return _popen(cmd, mode);
#else
return popen(cmd, mode);
#endif
}
void really_pclose(FILE* pipe) {
#ifdef _MSC_VER
_pclose(pipe);
#else
pclose(pipe);
#endif
}
std::string ExecuteProcess(std::string cmd) {
FILE* pipe = really_popen(cmd.c_str(), "r");
if (!pipe) throw std::runtime_error("Could not invoke command " + cmd);
char buffer[128];
std::string result = "";
while(!feof(pipe)) {
if(fgets(buffer, 128, pipe) != NULL)
result += buffer;
}
really_pclose(pipe);
return result;
}
}
This works just fine for me on Linux, but on Windows, it has a terrible habit of deadlocking- seems that fgets never returns. I've looked into the CRT sources and fgets eventually delegates to ReadFile, which never returns.
If I invoke the command from the command line, it returns within a second.
How can I read the output on Windows without deadlocking the parent?
If the child hasn't exited, then the call to fgets() won't exit, so you need to resolve why the child isn't exiting. The most likely cause in this sort of situation is that the child has hung because it has no standard input.
If that's the problem, you may be able to resolve it by changing the pipe mode to "rw". You won't typically need to do anything with the extra pipe, it just has to be there.
(As you mention in the comments, the problem can also be resolved by using the command shell redirection to give the child a handle to NUL as standard input.)
I want to get a Linux command's output string as well as command output status in a C++ program. I am executing Linux commands in my application.
for example:
Command:
rmdir abcd
Command output string:
rmdir: failed to remove `abcd': No such file or directory
Command Status:
1 (Which means command has been failed)
I tried using Linux function system() which gives the output status, and function popen() which gives me output string of a command, but neither function gives me both
the output string and output status of a Linux command.
The output string is in standard output or standard error descriptor (1 or 2, respectively).
You have to redirect these streams (take a look at dup and dup2 function) to a place, where you can read them (for example - a POSIX pipe).
In C I'd do something like this:
int pd[2];
int retValue;
char buffer[MAXBUF] = {0};
pipe(pd);
dup2(pd[1],1);
retValue = system("your command");
read(pd[0], buffer, MAXBUF);
Now, you have (a part of) your output in buffer and the return code in retValue.
Alternatively, you can use a function from exec (i.e. execve) and get the return value with wait or waitpid.
Update: this will redirect only standard output. To redirect standard error, use dup2(pd[1],1).
The simplest solution is to use system, and to redirect standard out and standard error to a temporarly file, which you can delete later.
Unfortunately there's no easy and simple way in C on Linux to do this. Here's an example how to read/write stdout/stderr/stdin of child process correctly.
And when you want to receive exit code you have to use waitpid (complete example is provided on the bottom of the provided page):
endID = waitpid(childID, &status, WNOHANG|WUNTRACED);
Now you just have to join those two together :)
There's also a great free book named Advanced Linux Programming (ALP) containing detailed information about these kinds of problem available here.
Building on Piotr Zierhoffer answer above, here's a function that does just that, and also restores stdout and stderr their original state.
// Execute command <cmd>, put its output (stdout and stderr) in <output>,
// and return its status
int exec_command(string& cmd, string& output) {
// Save original stdout and stderr to enable restoring
int org_stdout = dup(1);
int org_stderr = dup(2);
int pd[2];
pipe(pd);
// Make the read-end of the pipe non blocking, so if the command being
// executed has no output the read() call won't get stuck
int flags = fcntl(pd[0], F_GETFL);
flags |= O_NONBLOCK;
if(fcntl(pd[0], F_SETFL, flags) == -1) {
throw string("fcntl() failed");
}
// Redirect stdout and stderr to the write-end of the pipe
dup2(pd[1], 1);
dup2(pd[1], 2);
int status = system(cmd.c_str());
int buf_size = 1000;
char buf[buf_size];
// Read from read-end of the pipe
long num_bytes = read(pd[0], buf, buf_size);
if(num_bytes > 0) {
output.clear();
output.append(buf, num_bytes);
}
// Restore stdout and stderr and release the org* descriptors
dup2(org_stdout, 1);
dup2(org_stderr, 2);
close(org_stdout);
close(org_stderr);
return status;
}
you can use popen system call, it will redirect output to a file and from file you can redirect output to a string. like :
char buffer[MAXBUF] = {0};
FILE *fd = popen("openssl version -v", "r");
if (NULL == fd)
{
printf("Error in popen");
return;
}
fread(buffer, MAXBUF, 1, fd);
printf("%s",buffer);
pclose(fd);
For more information read man page for popen.
In my C++ program, I need to start a very long running new process and monitor its I/O. I cannot modify the source code of the program in question.
I was thinking of create a new thread and starting the process in it and sending the output continuously (which will be coming out asynchronously) to main thread.
My code for creating the process currently looks like this:
std::string SysExec::exec(char* cmd) {
FILE* pipe = popen(cmd, "r");
if (!pipe)
return "ERROR";
char buffer[128];
std::string result = "";
while (!feof(pipe)) {
if (fgets(buffer, 128, pipe) != NULL)
result += buffer;
}
pclose(pipe);
return result;
}
However, if called from main thread, it will make the main program stop (because of while (!feof(pipe))). How should I modify this? Or is there any better way to do this?
I need help to get the following to work. I need to start a bash process from c++, this bash process needs to accept input from stdin and output as per normal it's output to stdout.
From a different process I need to write commands to stdin which will then actually execute in bash as per above, then I'm interested in the result from stdout.
This is what I've tried so far, but the output does not make sense to me at all...
if (pipe(pipeBashShell)) {
fprintf(stderr, "Pipe error!\n");
exit(1);
}
if ((pipePId = fork()) == -1) {
fprintf(stderr, "Fork error. Exiting.\n"); /* something went wrong */
exit(1);
}
if (pipePId == 0) { //this is the child process
dup2(pipeBashShell[0], STDIN_FILENO);
dup2(pipeBashShell[1], STDOUT_FILENO);
dup2(pipeBashShell[1], STDERR_FILENO);
static char* bash[] = {"/bin/bash", "-i", NULL};
if (execv(*bash, bash) == -1) {
fprintf(stderr, "execv Error!");
exit(1);
}
exit(0);
} else {
char buf[512];
memset(buf, 0x00, sizeof(buf));
sprintf(buf, "ls\n");
int byteswritten = write(pipeBashShell[1], buf, strlen(buf));
int bytesRead = read(pipeBashShell[0], buf, sizeof(buf));
write(STDOUT_FILENO, buf, strlen(buf));
exit(0);
}
.
The output of the result above is as follows:
' (main)
bash:: command not found gerhard#gerhard-work-pc:~/workspaces/si/si$ gerhard
orkspaces/si/si$ gerhard# gerhard-work-pc:~/workspa
....
The command i'm trying to send to bash is "ls", which should give me a directory listing
Am I missing something here?
You have created one pipe (with two ends) and you are trying to use it for bi-directional communication -- from your main process to bash and vice versa. You need two separate pipes for that.
The way you have connected the file descriptors makes bash talk to itself -- it interprets its prompt as a command which it cannot find, and then interprets the error messages as subsequend commands.
Edit:
The correct setup works as follows:
prepare two pipes:
int parent2child[2], child2parent[2];
pipe(parent2child);
pipe(child2parent);
fork()
in the parent process:
close(parent2child[0]);
close(child2parent[1]);
// write to parent2child[1], read from child2parent[0]
in the child process:
close(parent2child[1]);
close(child2parent[0]);
dup2(parent2child[0], STDIN_FILENO);
dup2(child2parent[1], STDOUT_FILENO);