Сохранение в строку из трубы
У меня есть код, который работает /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;
}