Search code examples
cscanfpopen

saving to a string from a pipe


I have code here that runs /bin/ls -l and then prints out the output to the terminal, what I want to do here is save that output into a string for later use. I'm not sure how to go about doing this but from my guess it would look something like this

int main (){
    FILE *fp;
    int status;
    char path[100];
    char *s;
    fp = popen("/bin/ls -l", "r");
    if (fp == NULL){
        printf("fp error");
    }
    while(fgets(path,100,fp)!= NULL){
        printf("%s\n", path );
        scanf(path, s);
    }
    status = pclose(fp);
    if (status==-1){
        printf("pclose error");
    }else{
        printf("else on pclose\n");
    }
    return 0;
}

The while loop prints out my directory result no problem but I run into a segmentation fault : 11 at the end of it. What would be the correct way of approaching this problem?


Solution

  • To begin with while(fgets(path,100,fp)!= NULL){ already stores the first 99characters read from the pipe in path. There is no need to scanf anything else.

    100 is a horribly insufficient magic number to include in your code for the maximum path length. Better to use PATH_MAX defined in limits.h. (generally 4096, but is implementation defined). Which brings up another point, don't use magic numbers in your code, if you need a constant the system doesn't provide, then #define one, or use a global enum to define it.

    When reading with fgets, you must check for, and remove (or otherwise account for) the '\n' that will be included in the buffer filled by fgets (and POSIX getline). That also provides validation that you did in fact read a complete line of data. Otherwise, if the length of the line read is PATH_MAX - 1 and there was no '\n' at the end, the line was too long for the buffer and character for that line remain unread in your input stream. A simple call to strlen (path) and then checks with if/else if statements provide the validation and allow you to overwrite the trailing '\n' with a nul-terminating character.

    To handle the problem of "How many files to I need to provide for?", you can simply use a pointer-to-pointer-to-char and dynamically allocate pointers to each line and realloc additional pointers as required. You assign the address of each block of memory you allocate to store each line, to the individual pointers you allocate. Keep a count of the lines (for the pointer count) and realloc more pointers when you reach your limit (this works for reading from text files the same way) Don't forget to free the memory you allocate when you are done with it.

    Putting all the pieces together, you could do something like the following:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <limits.h>
    
    #define NFILES 16
    
    int main (){
    
        size_t i, n = 0,            /* number of files in listing    */
            nfiles = NFILES;        /* number of allocated pointers  */
        char path[PATH_MAX] = "",   /* buffer (PATH_MAX in limits.h) */
            **files = NULL;         /* pointer to pointer to file    */
        FILE *fp = popen("/bin/ls -l", "r");
    
        if (fp == NULL) {           /* validate pipe open for reading */
            perror ("popen");
            return 1;
        }
    
        /* allocate/validate initial number of pointers */
        if (!(files = malloc (nfiles * sizeof *files))) {
            perror ("malloc - files");
            return 1;
        }
    
        while (fgets (path, PATH_MAX, fp)) {    /* read each line */
    
            size_t len = strlen (path);         /* get length     */
            if (len && path[len - 1] == '\n')   /* validate '\n'  */
                path[--len] = 0;                /* trim '\n'      */
            else if (len + 1 == PATH_MAX) {     /* check path too long */
                fprintf (stderr, "error: path too long.\n");
                /* handle remaining chars in fp */
            }
    
            /* allocate/validate storage for line */
            if (!(files[n] = malloc (len + 1))) {
                perror ("malloc - files[n]");
                break;
            }
    
            strcpy (files[n++], path);          /* copy path */
    
            if (n == nfiles) {  /* realloc pointers as required */
                void *tmp = realloc (files, nfiles * 2 * sizeof *files);
                if (!tmp) {
                    perror ("realloc");
                    break;
                }
                files = tmp;
                nfiles *= 2;        /* increment current allocation */
            }
        }
    
    
        if (pclose (fp) == -1)      /* validate close */
            perror ("pclose");
    
        /* print and free the allocated strings */
        for (i = 0; i < n; i++) {
            printf ("%s\n", files[i]);    
            free (files[i]);        /* free individual file storage */
        }
        free (files);               /* free pointers */
    
        return 0;
    }
    

    Example Use/Output

    $ ./bin/popen_ls_files > dat/filelist.txt
    
    $ wc -l dat/filelist.txt
    1768 dat/filelist.txt
    
    $ cat dat/filelist.txt
    total 9332
    -rw-r--r--  1 david david     376 Sep 23  2014 3darrayaddr.c
    -rw-r--r--  1 david david     466 Sep 30 20:13 3darrayalloc.c
    -rw-r--r--  1 david david     802 Jan 25 02:55 3darrayfill.c
    -rw-r--r--  1 david david     192 Jun 27  2015 BoggleData.txt
    -rw-r--r--  1 david david    3565 Jun 26  2014 DoubleLinkedList-old.c
    -rw-r--r--  1 david david    3699 Jun 26  2014 DoubleLinkedList.c
    -rw-r--r--  1 david david    3041 Jun 26  2014 DoubleLinkedList.diff
    <snip>
    -rw-r--r--  1 david david    4946 May  7  2015 workers.c
    -rw-r--r--  1 david david     206 Jul 11  2017 wshadow.c
    -rw-r--r--  1 david david    1283 May 18  2015 wsininput.c
    -rw-r--r--  1 david david    5519 Oct 13  2015 xpathfname.c
    -rw-r--r--  1 david david     785 Sep 30 02:49 xrealloc2_macro.c
    -rw-r--r--  1 david david    2090 Sep  6 02:29 xrealloc_tst.c
    -rw-r--r--  1 david david    1527 Sep  6 03:22 xrealloc_tst_str.c
    -rwxr-xr--  1 david david     153 Aug  5  2014 xsplit.sh
    

    Memory Use/Error Check

    In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.

    For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.

    $ valgrind ./bin/popen_ls_files > /dev/null
    ==7453== Memcheck, a memory error detector
    ==7453== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
    ==7453== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
    ==7453== Command: ./bin/popen_ls_files
    ==7453==
    ==7453==
    ==7453== HEAP SUMMARY:
    ==7453==     in use at exit: 0 bytes in 0 blocks
    ==7453==   total heap usage: 1,777 allocs, 1,777 frees, 148,929 bytes allocated
    ==7453==
    ==7453== All heap blocks were freed -- no leaks are possible
    ==7453==
    ==7453== For counts of detected and suppressed errors, rerun with: -v
    ==7453== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
    

    Always confirm that you have freed all memory you have allocated and that there are no memory errors.

    Look things over and let me know if you have further questions.