Search code examples
cfile-iomallocdynamic-memory-allocation

How could I allocate a variable length command output?


I have a bash script which prints a single huge line of variable output. All the examples I've seen uses a fixed buffer of 1024 bytes or so, in order to read line by line.

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

int main( int argc, char *argv[] ) {

  FILE *fp;
  char path[1024];

  /* Open the command for reading. */
  fp = popen("bash /home/ouhma/myscript.sh", "r");
  if (fp == NULL) {
    printf("Failed to run command\n" );
    exit(1);
  }

  /* Read the output a line at a time - output it. */
  while (fgets(path, sizeof(path)-1, fp) != NULL) {
    printf("%s", path);
  }

  /* close */
  pclose(fp);

  return 0;
}

Link reference: C: Run a System Command and Get Output?

But what if I don't know if the output line has a length even bigger of 1024 bytes? How could I handle it through reading with popen() command?


Solution

  • But what if I don't know if the output line has a length even bigger of 1024 bytes

    You then need to handle the storage for incoming data dynamically.

    To do so you would additionally to what you show add a dynamically allocated "string" which grows if fully used by reallocating it to provide more room.

    Code doing so might look like this:

    #include <stdlib.h>
    #include <stdio.h>
    
    
    #define BUFFER_SIZE (1024)
    #define START_SIZE (1) /* Has to be > 0 */
    
    
    int main(void) 
    {
      size_t s = START_SIZE;
      char * path = malloc(s);
      if (NULL == path)
      {
        perror("malloc() failed");
        return EXIT_FAILURE);
      }
    
      path[0] = '\0';
    
      {
        /* Open the command for reading. */
        FILE * fp = popen("bash /home/ouhma/myscript.sh", "r");
        if (NULL == fp) 
        {
          perror("popen() failed");
          return EXIT_FAILURE); /* By returning here the code leaks the memory
                                   already allocated to path as well as fp. */
        }
    
        {
          char buffer[BUFFER_SIZE];
    
          /* Read the output a line at a time - output it. */
          while (NULL != fgets(buffer, sizeof buffer, fp)) 
          {
            fprintf(stderr, "partly read: '%s'\n", buffer);
    
            while ((s - 1) < strlen(buffer))
            {
              void * p = realloc(path, s *= 2); /* Grow s exponentially. */
              if (NULL == p)
              {
                perror("realloc() failed");
                return EXIT_FAILURE; /* By returning here the code leaks the memory
                                        already allocated to path as well as fp. */
              }
    
              path = p;
            }
    
            /* Concatenate what just had been read to final "string". */
            strcat(path, buffer);
          }
        }
    
        if (!feof(fp))
        {
          perror("fgets() failed");
        }
    
        /* Close file. */
        if (-1 == pclose(fp))
        {
          perror("pclose() failed");
        }
      }
    
      /* Print result. */
      printf("read: '%s'\n", path);
    
      /* Clean up. */
      free(path);
    
      return EXIT_SUCCESS;
    }
    

    Clean up of open file descriptors and dynamically allocated memory in case of any failure is left as an exercise to the reader ... ;-)