Сохранение в строку из трубы

У меня есть код, который работает /bin/ls -l а затем распечатывает вывод на терминал, что я хочу сделать, это сохранить этот вывод в строку для дальнейшего использования. Я не уверен, как это сделать, но, думаю, это будет выглядеть примерно так

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;
}

Цикл while выводит мой результат каталога без проблем, но я сталкиваюсь с ошибкой сегментации: 11 в конце. Каков будет правильный подход к этой проблеме?

2 ответа

Начать с while(fgets(path,100,fp)!= NULL){ уже хранит первый 99символы читаются из трубы в path, Там нет необходимости scanf что-нибудь еще.

100 является чудовищно недостаточным магическим числом для включения в ваш код для максимальной длины пути. Лучше использовать PATH_MAX определяется в limits.h, (в общем-то 4096, но определяется реализацией). Это поднимает другой вопрос: не используйте магические числа в своем коде, если вам нужна константа, которую система не предоставляет, тогда #define один или использовать глобальный enum определить это.

При чтении с fgetsнеобходимо проверить и удалить (или иным образом учесть) '\n' который будет включен в буфер, заполненный fgets (и POSIX getline). Это также обеспечивает проверку того, что вы действительно прочитали полную строку данных. В противном случае, если длина прочитанной строки PATH_MAX - 1 и не было '\n' в конце строка была слишком длинной для буфера, а символ этой строки остался непрочитанным в вашем входном потоке. Простой вызов strlen (path) а затем проверяет с if/else if заявления обеспечивают проверку и позволяют перезаписать конечный '\n' с нулевым завершающим символом.

Чтобы решить проблему "Сколько файлов мне нужно предоставить?", Вы можете просто использовать указатель на указатель на символ и динамически размещать указатели для каждой строки и realloc дополнительные указатели по мере необходимости. Вы назначаете адрес каждого блока памяти, который вы выделяете для хранения каждой строки, отдельным указателям, которые вы выделяете. Вести счетчик строк (для счетчика указателей) и realloc больше указателей, когда вы достигнете своего предела (это работает для чтения из текстовых файлов таким же образом) Не забудьте освободить память, которую вы выделяете, когда вы закончите с ним.

Собрав все части вместе, вы можете сделать что-то вроде следующего:

#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;
}

Пример использования / Вывод

$ ./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

Использование памяти / проверка ошибок

В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанности в отношении любого выделенного блока памяти: (1) всегда сохранять указатель на начальный адрес для блока памяти, поэтому (2) его можно освободить, если его нет больше нужно

Для Linux valgrind это нормальный выбор. Есть похожие проверки памяти для каждой платформы. Все они просты в использовании, просто запустите вашу программу через него.

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

Всегда проверяйте, что вы освободили всю выделенную память и что ошибок памяти нет.

Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

Я предполагаю, что вы хотите сохранить каталоги в массив строк. В этом случае массив s[] сохраняет ссылки на все ваши записи. Каждая прочитанная запись получит выделенную память для записи и терминатора строки \0,

Стартовая программа может выглядеть так:

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

#define MAX_NR_OF_ENTRIES 10000
#define PATH_LEN    256

int main (){
    FILE *fp;
    int status;
    char path[PATH_LEN];
    char *s[MAX_NR_OF_ENTRIES]; 
    int i,j = 0;

    fp = popen("/bin/ls -l", "r");

    if (fp == NULL){
        printf("fp error\n");
    }

    while(fgets(path,PATH_LEN,fp) != NULL){

        printf("%s\n", path );

        s[i] = malloc(strlen(path)+1);
        strcpy(s[i],path);
        i++;

        if(i>=MAX_NR_OF_ENTRIES) 
        {
            printf("MAX_NR_OF_ENTRIES reached!\n");     
            break;
        }
    }


    status = pclose(fp);
    if (status==-1){
        printf("pclose error");
    }else{
        printf("pclose was fine!\n");
    }

    // Print and free the allocated strings
    for(j=0; j< i; j++){
        printf("%s\n", s[j] );    
        free (s[j]);        
    }

    return 0;
}

Это работает, но мы поместили 10000 указателей в стек. Но, как предположил Дэвид К. Ранкин, мы могли бы выделить массив массивов s также динамически. Стартовая рабочая программа:

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

#define MAX_NR_OF_ENTRIES 10
#define PATH_LEN    256

int main (){
    FILE *fp;
    int status;
    char path[PATH_LEN];

    int i,j = 0;

    size_t nr_of_elements = MAX_NR_OF_ENTRIES;

    char **old_arr;
    char **new_arr;

    char **s = calloc(nr_of_elements, sizeof(char*));

    fp = popen("/bin/ls -l", "r");

    if (fp == NULL){
        printf("fp error\n");
    }

    while(fgets(path,PATH_LEN,fp) != NULL){

        printf("%s\n", path );

        char *str = malloc(strlen(path)+1);
        strcpy(str, path);
        s[i] = str;

        i++;

        if(i>=nr_of_elements ) 
        {
            printf("resizing\n");     
            size_t old_size = nr_of_elements; 
            nr_of_elements = 4*nr_of_elements; // increase size for `s` 4 times (you can invent something else here)
            old_arr = s; 

            // allocating a bigger copy of the array s, copy it inside, and redefine the pointer:

            new_arr = calloc(nr_of_elements, sizeof(char*));

            if (!new_arr) 
             {
                 perror("new calloc failed");
                 exit(EXIT_FAILURE);
             }

            memcpy (new_arr, old_arr, sizeof(char*)*old_size);   // notice the use of `old_size` 

            free (old_arr);
            s = new_arr;
         }
    }


    status = pclose(fp);
    if (status==-1){
        printf("pclose error");
    }else{
        printf("pclose was fine!\n");
    }

    // Print and free the allocated strings
    for(j=0; j< i; j++){
        printf("%s\n", s[j] );    
        free (s[j]);        
    }

    free(s);
    return 0;
}
Другие вопросы по тегам