6

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.

SwapG
  • 93
  • 1
  • 6

5 Answers5

7

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).

Piotr Zierhoffer
  • 5,005
  • 1
  • 38
  • 59
  • Are you sure this works? I thought `system()` doesn't return until the shell process has exited. Also, if the child's output is large its writes to `stdout` may block, causing a deadlock. I guess I won't be surprised if it does work as the data is already in the pipe when the child exits, but still the deadlock may be an issue. – FatalError Apr 02 '13 at 12:57
  • Well, this might block if the size of the output is larger than the size of a pipe (65536 by default on my machine). If this is expected, then fork + exec is the preferred way. But if not (I mean, if the case is known and the output size is bounded), than why bother? :) – Piotr Zierhoffer Apr 02 '13 at 13:02
  • Sure, fair enough. +1 for the interesting strategy – FatalError Apr 02 '13 at 13:07
  • It's hard to imagine a case where the output size is really bound. The usual solution here is to redirect the output of the command to a temporary file, and then read that. – James Kanze Apr 02 '13 at 13:12
  • I'd say it's really easy to imagine. For example when you run your own program or you know the source code. I haven't tested it, but I believe that it's stderr is usually a one-liner, probably only "is not empty" or "does not exist". Of course, redirection through a file is just another great option. There are so many of them and that's why we like Linux :) – Piotr Zierhoffer Apr 02 '13 at 13:14
  • I don't think any of the standard utilities have an upper bound on the output. Realistically, of course, the standard error output from `rmdir` with a single directory name (his example) is not going to exceed the size of a pipe (even on very old Unix, where the size of a pipe was 4096 bytes), but I doubt you'll find it "guaranteed" anywhere. – James Kanze Apr 02 '13 at 13:23
  • 1
    Ok, I've never used the word "guaranteed". By "bound" I've had in mind that you _know_ the output to be reasonable in size, maybe because you're the one who generates the output. Of course, there is always a possibility of a disaster, bash failure, fork failure, !ANY! failure, but usually we just check for reasonable conditions. If the system is not critical/generic/widely used, than maybe using the first option that comes to your mind is good _enough_. But I vote for your answer anyway, because we can assume that you have enough disk storage space for your output :P – Piotr Zierhoffer Apr 02 '13 at 13:29
3

The simplest solution is to use system, and to redirect standard out and standard error to a temporarly file, which you can delete later.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
2

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.

Community
  • 1
  • 1
Vyktor
  • 20,559
  • 6
  • 64
  • 96
1

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;
}
Yuval
  • 110
  • 6
0

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.

Abhitesh khatri
  • 2,911
  • 3
  • 20
  • 29