12

I am writing a baby program for practice. What I am trying to accomplish is basically a simple little GUI which displays services (for Linux); with buttons to start, stop, enable, and disable services (Much like the msconfig application "Services" tab in Windows). I am using C++ with Qt Creator on Fedora 21.

I want to create the GUI with C++, and populating the GUI with the list of services by calling bash scripts, and calling bash scripts on button clicks to do the appropriate action (enable, disable, etc.)

But when the C++ GUI calls the bash script (using system("path/to/script.sh")) the return value is only for exit success. How do I receive the output of the script itself, so that I can in turn use it to display on the GUI?

For conceptual example: if I were trying to display the output of (systemctl --type service | cut -d " " -f 1) into a GUI I have created in C++, how would I go about doing that? Is this even the correct way to do what I am trying to accomplish? If not,

  1. What is the right way? and
  2. Is there still a way to do it using my current method?

I have looked for a solution to this problem but I can't find information on how to return values from Bash to C++, only how to call Bash scripts from C++.

2
  • Welcome to Stack Overflow! I've edited your question to fix formatting and make it clearer/more concise. Thanks for providing so much detail! Please see How to Ask and the help center if you have any questions about asking questions. Commented Aug 16, 2015 at 21:00
  • Even thought I applaud your enthusiasm taking on the task of writing your app in C/C++ to call systemctl, you might want to look at the GUI's zenity or kdialog (depending on your desktop), that are designed to provide a simple GUI interface to shell commands. Even if you end up writing the code in C++, zenity/kdialog are helpful for prototyping. Commented Aug 16, 2015 at 21:55

3 Answers 3

11

We're going to take advantage of the popen function, here.

std::string 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;
}

This function takes a command as an argument, and returns the output as a string.

NOTE: this will not capture stderr! A quick and easy workaround is to redirect stderr to stdout, with 2>&1 at the end of your command.

Here is documentation on popen. Happy coding :)

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks a bunch, this is perfect! Short, concise and clear. Awesome thanks again!
@Rhurac, No problem, and welcome to StackOverflow! Enjoy your stay!
5

You have to run the commands using popen instead of system and then loop through the returned file pointer.

Here is a simple example for the command ls -l

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *process;
    char buff[1024];

    process = popen("ls -l", "r");

    if (process != NULL) {
        while (!feof(process)) {
            fgets(buff, sizeof(buff), process);
            printf("%s", buff);
        }

        pclose(process);
    }

    return 0;
}

3 Comments

Yes, but if someone copy pasta's your code, and it doesn't terminate where you thought it would, it would result in zombie processes. It never hurts to be explicit! :)
Thanks for the quick response this was great thanks!
Your use of while (!feof(process)) will result in the final line of output being duplicated. The proper manner is while (fgets(buff, sizeof(buff), process)) printf("%s", buff);
3

The long approach - which gives you complete control of stdin, stdout, and stderr of the child process, at the cost of fairly significant complexity - involves using fork and execve directly.

  1. Before forking, set up your endpoints for communication - pipe works well, or socketpair. I'll assume you've invoked something like below:

    int childStdin[2], childStdout[2], childStderr[2];
    pipe(childStdin);
    pipe(childStdout);
    pipe(childStderr);
    
  2. After fork, in child process before execve:

    dup2(childStdin[0], 0);  // childStdin read end to fd 0 (stdin)
    dup2(childStdout[1], 1); // childStdout write end to fd 1 (stdout)
    dup2(childStderr[1], 2); // childStderr write end to fd 2 (stderr)
    

    .. then close all of childStdin, childStdout, and childStderr.

  3. After fork, in parent process:

     close(childStdin[0]);  // parent cannot read from stdin
     close(childStdout[1]); // parent cannot write to stdout/stderr
     close(childStderr[1]);
    

Now, your parent process has complete control of the std i/o of the child process - and must safely multiplex childStdin[1], childStdout[0], and childStderr[0], while also monitoring for SIGCLD and eventually using a wait-series call to check the process termination code. pselect is particularly good for dealing with SIGCLD while dealing with std i/o asynchronously. See also select or poll of course.

If you want to merge the child's stdout and stderr, just dup2(childStdout[1], 2) and get rid of childStderr entirely.

The man pages should fill in the blanks from here. So that's the hard way, should you need it.

3 Comments

Thanks a lot for this answer this looks like super useful information. I will definitely save this and explore it in the future. Thanks for answering so quickly!
It is not so much of a long approach as it is more a lower level approach. Both have their places, If dealing without output from a single command popen provides a simple interface, but when you need access to the information from parts of your code (multiple functions, etc...), fork, dup, dup2 provides a flexible way to meet your needs.
True. If popen solves the problem, it solves it with substantially less code. This is the way you go when popen is insufficient.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.