Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting stdout from an executed application

Does anyone know how to catch the output (I think its stdout) from execvp instead of the system printing it (in c on linux) in the terminal?

like image 801
topherg Avatar asked Dec 08 '11 02:12

topherg


4 Answers

execvp replaces the current running process in memory. There's no "catching" the output.

I suspect you're trying to run an external process from an existing process, and parse its output. For that you need to use popen() which does a fork() then an exec(), returning a FILE * to read (which will be the stdout of the process you just ran).

like image 160
Brian Roach Avatar answered Nov 12 '22 08:11

Brian Roach


I distrust popen/pclose, as I've worked on too many systems where SIGCHLD was handled slightly differently. And I distrust the sh-shell parsing used by popen, since I rarely use it.

The short 22-year-old O'Reilly book Using C on the UNIX System, by Dave Curry is still a very good reference for this sort of stuff.

Anyway, here is some code. It is a bit lengthy, as it parses the sample string "/bin/ls /etc" into the array {"/bin/ls", "/etc", 0}. But I find using the string format easier and shorter 98% of the time, although this example belies that.

This code generates a listing of /etc. You'll need to change some stuff like e.g. NUMBER() which is the same asXtNumber(). And you'll need to decide whether it matches your handling of SIGCHLD.

int main(void) {  // list the files in /etc
   char buf[100];
   FILE *fp;
   int pid = spawnfp("/bin/ls /etc", &fp);
   while (fgets(buf, sizeof buf, fp))
      printf("%s", buf);
   fclose(fp);                    // pclose() replacement
   kill(pid, SIGKILL);            // pclose() replacement
   return 0;
}

The subroutines here are:

static int spawnpipe(const char *argv[], int *fd) // popen() replacement
{
   int pid;
   int pipe_fds[2];

   if (pipe(pipe_fds) < 0)
      FatalError("pipe");

   switch ((pid = fork()))
   {
      case -1:
         FatalError("fork");
      case 0:                     // child
         close(1);
         close(2);
         dup(pipe_fds[0]);
         dup(pipe_fds[1]);
         close(pipe_fds[0]);
         close(pipe_fds[1]);

         execv(argv[0], (char * const *)argv);
         perror("execv");
         _exit(EXIT_FAILURE);    // sic, not exit()
      default:
         *fd = pipe_fds[0];
         close(pipe_fds[1]);
         return pid;
   }
}

This converts an ascii string to an argv list, which is probably useless to you:

Bool convertStringToArgvList(char *p, const char **argv, int maxNumArgs)
{
   // Break up a string into tokens, on spaces, except that quoted bits, 
   // with single-quotes, are kept together, without the quotes. Such  
   // single-quotes cannot be escaped. A double-quote is just an ordinary char.
   // This is a *very* basic parsing, but ok for pre-programmed strings.
   int cnt = 0;
   while (*p)
   {
      while (*p && *p <= ' ')    // skip spaces
         p++;
      if (*p == '\'')            // single-quote block
      {
         if (cnt < maxNumArgs)
            argv[cnt++] = ++p;   // drop quote
         while (*p && *p != '\'')
            p++;
      }
      else if (*p)               // simple space-delineated token
      {
         if (cnt < maxNumArgs)
            argv[cnt++] = p;
         while (*p > ' ')
            p++;
      }
      if (*p)
         *p++ = 0;               // nul-terminate
   }
   if (cnt < maxNumArgs)
      argv[cnt++] = 0;
   return cnt <= maxNumArgs;     // check for too many tokens (unlikely)
}

This converts the argument string to tokens and, more importantly, the fd to an fp, since the OP requested stdout:

int spawnfp(const char *command, FILE **fpp)
{
   const char *argv[100];
   int fd, pid;
   if (!convertStringToArgvList(strdupa(command), argv, NUMBER(argv)))
      FatalError("spawnfp");
   pid = spawnpipe(argv, &fd);
   *fpp = fdopen(fd, "r");
   return pid;
}
like image 38
Joseph Quinsey Avatar answered Nov 12 '22 09:11

Joseph Quinsey


See the documentation of popen, I think it's exactly what you need.

like image 1
Seth Carnegie Avatar answered Nov 12 '22 10:11

Seth Carnegie


As others have said, popen is what you want to use. Something like this...

#include <iomanip>
#include <iostream>

using namespace std;

const int MAX_BUFFER = 255;

int main()
{
        string cmd;
        cout << "enter cmd: ";
        cin >> cmd;
        cout << endl << "running " << cmd << "…" << endl;


        string stdout;
        char buffer[MAX_BUFFER];
        FILE *stream = popen(cmd.c_str(), "r");
        while ( fgets(buffer, MAX_BUFFER, stream) != NULL )
        stdout.append(buffer);
        pclose(stream);


        cout << endl << "output: " << endl << stdout << endl;
}
like image 1
Ternary Avatar answered Nov 12 '22 08:11

Ternary