Более элегантный способ разбора - PullRequest
0 голосов
/ 17 мая 2018

Я новичок в C. Мне нужно написать небольшую функцию, которая открывает файл конфигурации, который имеет 3 строки, каждая строка содержит путь к файлам / каталогам, которые мне нужно извлечь.

Я написал эту программу, и она, кажется, работает:

void readCMDFile(char* cmdFile,char directoryPath[INPUT_SIZE], char inputFilePath[INPUT_SIZE],char outputFilePath [INPUT_SIZE]) {
  //open files
  int file = open(cmdFile, O_RDONLY);
  if (file < 0) {
    handleFailure();
  }
  char buffer[BUFF_SIZE];
  int status;
  int count;
  while((count=read(file,buffer,sizeof(buffer)))>0)
  {
    int updateParam = UPDATE1;
    int i,j;
    i=0;
    j=0;
    for (;i<count;i++) {
      if (buffer[i]!='\n'&&buffer[i]!=SPACE&&buffer[i]!='\0') {
        switch (updateParam){
          case UPDATE1:
            directoryPath[j] = buffer[i];
            break;
          case UPDATE2:
            inputFilePath[j] = buffer[i];
            break;
          case UPDATE3:
            outputFilePath[j] = buffer[i];
            break;
        }
        j++;

      } else{
        switch (updateParam){
          case UPDATE1:
            updateParam = UPDATE2;
            j=0;
            break;
          case UPDATE2:
            updateParam = UPDATE3;
            j=0;
            break;

        }

      }
    }
  }
  if (count < 0) {
    handleFailure();
  }

}

но это невероятно не интуитивно и довольно некрасиво, поэтому я подумал, что должен быть более элегантный способ сделать это. есть какие-нибудь предложения? Спасибо!

Обновление: содержимое файла конфигурации будет выглядеть так:

/home/bla/dirname
/home/bla/bla/file1.txt
/home/bla/bla/file2.txt

Ответы [ 4 ]

0 голосов
/ 17 мая 2018

В вашей программе вы передаете 3 массив char для хранения 3 строк, прочитанных из файла. Но это очень неэффективно, поскольку входной файл может содержать больше строк, и в будущем вам может потребоваться прочитать из файла более 3 строк. Вместо этого вы можете передать массив указателей char, выделить им память и скопировать содержимое строк, считанных из файла. Как отметил Джонатан (в комментарии), если вы используете стандартный ввод / вывод, то вы можете использовать функцию типа fgets() для чтения строк из входного файла.
Считайте строку из файла и выделите память для указателя и скопируйте строку, прочитав из нее файл. Если строка слишком длинная, вы можете прочитать оставшуюся часть в последовательных вызовах на fgets() и использовать realloc для расширения существующей памяти, указатель указывает на достаточно большой размер, чтобы вместить оставшуюся часть прочитанной строки.

Собрав все это вместе, вы можете сделать:

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

#define BUF_SZ    100
#define MAX_LINES 3  /* Maximum number of lines to be read from file */

int readCMDFile(const char* cmdFile, char *paths[MAX_LINES]) {
    int count, next_line, line_cnt, new_line_found;
    char tmpbuf[BUF_SZ];
    FILE *fp;

    fp = fopen(cmdFile, "r");
    if (fp == NULL) {
        perror ("Failed to open file");
        return -1;
    }

    next_line = 1; /* Keep track of next line */
    count = 1;     /* Used to calculate the size of memory, if need to reallocte 
                    * in case when a line in the file is too long to read in one go */
    line_cnt = 0;  /* Keep track of index of array of char pointer */
    new_line_found = 0; 

    while ((line_cnt < MAX_LINES) && (fgets (tmpbuf, BUF_SZ, fp) != NULL)) {
        if (tmpbuf[strlen(tmpbuf) - 1] == '\n') {
            tmpbuf[strlen(tmpbuf) - 1] = '\0';
            new_line_found = 1;
        } else {
            new_line_found = 0;
        }

        if (next_line) {
            paths[line_cnt] = calloc (sizeof (tmpbuf), sizeof (char));
            if (paths[line_cnt] == NULL) {
                perror ("Failed to allocate memory");
                return -1;
            }
            next_line = 0;
            count = 1;
        } else {
            char *ptr = realloc (paths[line_cnt], sizeof (tmpbuf) * (++count));
            if (ptr == NULL) {
                free (paths[line_cnt]);
                perror ("Failed to reallocate memory");
                return -1;
            } else {
                paths[line_cnt] = ptr;
            }
        }

        /* Using strcat to copy the buffer to allocated memory because
         * calloc initialize the block of memory with zero, so it will
         * be same as strcpy when first time copying the content of buffer 
         * to the allocated memory and fgets add terminating null-character 
         * to the buffer so, it will concatenate the content of buffer to 
         * allocated memory in case when the pointer is reallocated */
        strcat (paths[line_cnt], tmpbuf);
        if (new_line_found) {
            line_cnt++;
            next_line = 1;
        }
    }

    fclose(fp);
    return line_cnt;
}

int main(void) {
    int lines_read, index;
    const char *file_name = "cmdfile.txt";
    char *paths[MAX_LINES] = {NULL};

    lines_read = readCMDFile(file_name, paths);
    if (lines_read < 0) {
        printf ("Failed to read file %s\n", file_name);
    }

    /* Check the output */
    for (index = 0; index < lines_read; index++) {
        printf ("Line %d: %s\n", index, paths[index]);
    }

    /* Free the allocated memory */
    for (index = 0; index < lines_read; index++) {
        free (paths[index]);
        paths[index] = NULL;
    }

    return 0;
}

Выход:

$ cat cmdfile.txt
/home/bla/dirname
/home/bla/bla/file1.txt
/home/bla/bla/file2.txt

$ ./a.out
Line 0: /home/bla/dirname
Line 1: /home/bla/bla/file1.txt
Line 2: /home/bla/bla/file2.txt

Обратите внимание, что вышеприведенная программа не заботится о пустых строках в файле, поскольку она не была упомянута в вопросе. Но если вы хотите, вы можете добавить эту проверку сразу после удаления завершающего символа новой строки из строки, прочитанной из файла.

0 голосов
/ 17 мая 2018

Рекомендую заглянуть в регулярные выражения . Таким образом, вы читаете все, затем сопоставляете с регулярными выражениями и обрабатываете свои совпадения.

Для этого существуют регулярные выражения: сделать синтаксический анализ элегантным.

0 голосов
/ 17 мая 2018

Ваш вопрос не в том, чтобы проанализировать содержимое файла, а в том, чтобы просто прочитать строки файла в адекватном хранилище внутри функции таким образом, чтобы объект, содержащий сохраненные строки, мог быть возвращен вызывающемуфункция.Это довольно стандартно, но у вас есть несколько способов приблизиться к нему.

Самое большое соображение - не знать длину строк, которые нужно прочитать.Вы говорите, что в настоящее время нужно прочитать 3 строки, но не нужно заранее знать, сколько строк (зная - вы можете избежать realloc, но это единственная экономия)

Вы хотите создать как можно более надежный и гибкий метод для чтения строк и их сохранения таким образом, чтобы выделить достаточно памяти для хранения прочитанного.Хороший подход состоит в том, чтобы объявить временный буфер фиксированного размера для хранения каждой строки, считанной из файла с fgets, а затем вызвать strlen в буфере, чтобы определить необходимое количество символов (а также обрезать завершающий символ новой строки).на fgets) Поскольку вы читаете информацию о пути, предопределенный макрос PATH_MAX может использоваться для адекватного размера вашего временного буфера, чтобы гарантировать, что он может содержать путь максимального размера, используемый системой.Вы также можете использовать POSIX geline вместо fgets, но сейчас мы будем придерживаться стандартной библиотеки C.

Базовый тип, который позволит вам выделить память для нескольких строк в вашей функции ивернуть единственный указатель, который вы можете использовать в вызывающей функции: char ** ( указатель на указатель на символ - или произвольно динамический массив указателей ).Схема проста: вы выделяете некоторое начальное количество указателей (3 в вашем случае), а затем перебираете файл, читая строку за раз, получая длину строки, а затем выделяя length + 1 символов хранения длядержать строй.Например, если вы выделите 3 указателя с помощью:

#define NPATHS 3
...
char **readcmdfile (FILE *fp, size_t *n)
{
    ...
    char buf[PATH_MAX] = "";    /* temp buffer to hold line */
    char **paths = NULL;        /* pointer to pointer to char to return */
    size_t idx = 0;             /* index counter (avoids dereferencing) */
    ...
    paths = calloc (NPATHS, sizeof *paths); /* allocate NPATHS pointers */
    if (!paths) {                   /* validate allocation/handle error */
        perror ("calloc-paths");
        return NULL;
    }
    ...
    while (idx < NPATHS && fgets (buf, sizeof buf, fp)) {
        size_t len = strlen (buf);          /* get length of string in buf */
        ...
        paths[idx] = malloc (len + 1);      /* allocate storage for line */
        if (!paths[idx]) {                  /* validate allocation */
            perror ("malloc-paths[idx]");   /* handle error */
            return NULL;
        }
        strcpy (paths[idx++], buf);     /* copy buffer to paths[idx] */
        ...
    return paths;   /* return paths */
}

( note: , вы можете отменить ограничение idx < NPATHS, если включить проверку перед выделением для каждой строки и *На 1026 * больше указателей, по мере необходимости)

Остальное - просто обработка открытия файла и передача открытого файлового потока в вашу функцию.Основной подход состоит в том, чтобы либо указать имя файла в командной строке, а затем открыть имя файла с fopen (или прочитать из stdin по умолчанию, если имя файла не указано).Как и на каждом шаге в вашей программе, вам нужно проверить возвращаемые значения и обработать любую ошибку , чтобы избежать обработки мусора (и вызова Undefined Behavior )

Простым примером будет:

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

    char **paths;       /* pointer to pointer to char for paths */
    size_t i, n = 0;    /* counter and n - number of paths read */
    /* open file given by 1st argument (or read stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("fopen-failed");
        return 1;
    }

    paths = readcmdfile (fp, &n);   /* call function to read file */
                                    /* passing open file pointer */
    if (!paths) {   /* validate return from function */
        fprintf (stderr, "error: readcmdfile failed.\n");
        return 1;
    }

    for (i = 0; i < n; i++) {   /* output lines read from file */
        printf ("path[%lu]: %s\n", i + 1, paths[i]);
        free (paths[i]);        /* free memory holding line */
    }
    free (paths);   /* free pointers */

    return 0;
}

Соединение всех частей вместе, добавление кода обрезки для чтения '\n' и включение его в buf на fgets, и добавление дополнительного теста кубедитесь, что строка, которую вы прочитали, действительно помещается в buf, вы можете сделать что-то вроде этого:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h> /* for PATH_MAX */

#define NPATHS 3

/* read lines from file, return pointer to pointer to char on success
 * otherwise return NULL. 'n' will contain number of paths read from file.
 */
char **readcmdfile (FILE *fp, size_t *n)
{
    char buf[PATH_MAX] = "";    /* temp buffer to hold line */
    char **paths = NULL;        /* pointer to pointer to char to return */
    size_t idx = 0;             /* index counter (avoids dereferencing) */
    *n = 0;                     /* zero the pointer passed as 'n' */

    paths = calloc (NPATHS, sizeof *paths); /* allocate NPATHS pointers */
    if (!paths) {                   /* validate allocation/handle error */
        perror ("calloc-paths");
        return NULL;
    }

    /* read while index < NPATHS & good read into buf
     * (note: instead of limiting to NPATHS - you can simply realloc paths
     *  when idx == NPATHS -- but that is for later)
     */
    while (idx < NPATHS && fgets (buf, sizeof buf, fp)) {
        size_t len = strlen (buf);          /* get length of string in buf */
        if (len && buf[len - 1] == '\n')    /* validate last char is '\n' */
            buf[--len] = 0;                 /* overwrite '\n' with '\0' */
        else if (len == PATH_MAX - 1) { /* check buffer full - line to long */
            fprintf (stderr, "error: path '%lu' exceeds PATH_MAX.\n", idx);
            return NULL;
        }
        paths[idx] = malloc (len + 1);      /* allocate storage for line */
        if (!paths[idx]) {                  /* validate allocation */
            perror ("malloc-paths[idx]");   /* handle error */
            return NULL;
        }
        strcpy (paths[idx++], buf);     /* copy buffer to paths[idx] */
    }
    *n = idx;       /* update 'n' to contain index - no. of lines read */

    return paths;   /* return paths */
}

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

    char **paths;       /* pointer to pointer to char for paths */
    size_t i, n = 0;    /* counter and n - number of paths read */
    /* open file given by 1st argument (or read stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("fopen-failed");
        return 1;
    }

    paths = readcmdfile (fp, &n);   /* call function to read file */
                                    /* passing open file pointer */
    if (!paths) {   /* validate return from function */
        fprintf (stderr, "error: readcmdfile failed.\n");
        return 1;
    }

    for (i = 0; i < n; i++) {   /* output lines read from file */
        printf ("path[%lu]: %s\n", i + 1, paths[i]);
        free (paths[i]);        /* free memory holding line */
    }
    free (paths);   /* free pointers */

    return 0;
}

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

Пример входного файла

$ cat paths.txt
/home/bla/dirname
/home/bla/bla/file1.txt
/home/bla/bla/file2.txt

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

$ ./bin/readpaths <paths.txt
path[1]: /home/bla/dirname
path[2]: /home/bla/bla/file1.txt
path[3]: /home/bla/bla/file2.txt

Как вы можете видеть, функция просто прочитала каждую строку входного файла, присвоила 3 ​​указателя, присвоила каждой строке и присвоила адрес для каждого блока соответствующемууказатель, а затем возвращает указатель на коллекциюmain() там, где ему назначено paths.Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

0 голосов
/ 17 мая 2018

На вашем месте я создам метод для блоков if / else. Я чувствую, что они излишни.

 switch(updateParam) {
     case UPDATE1:
            method(); /*do if/else here*/
            break;
     ...............
     ...............
 }

Однако вы все равно можете поместить их туда, если вам не нужен метод для других случаев, и вы беспокоитесь о проблемах производительности, поскольку вызов функции стоит больше, чем просто коллективные инструкции.

...