C программа для чтения файла с чтением дополнительной строки - PullRequest
0 голосов
/ 04 ноября 2019

Код, над которым я работаю, включает чтение файла с вводом, структурированного следующим образом:

(spaces)name(spaces) val (whatever) \n
(spaces)name(spaces) val (whatever) \n
(spaces)name(spaces) val (whatever) \n

Где пробелы означают произвольное количество пробелов. Мой код должен давать как имя, так и значение. Есть еще одно условие, когда все в строке после «#» игнорируется (рассматривается как комментарий). Выходные данные должны быть:

"name: (name) value: val \n" 

Для большей части кода код работает, за исключением того, что он добавляет дополнительную строку, где он создаст набор name = null и val для того, что было последним прочитанным числом. Например, мой тестовый файл:

a 12
b     33
#c 15
nice 6#9

Вывод:

Line after:  a 12

name: a value: 12 :
Line after: b     33 

name: b  value: 33 :
Line after:  # c 15

Line after:  nice 6#9

name: nice value: 6 :
Line after:

name:  value: 6 : //why is this happening

Код здесь.

void readLine(char *filename)
{
    FILE *pf;
    char name[10000]; 
    char value[20];
    pf = fopen(filename, "r");
    char line[10000];
    if (pf){
        while (fgets(line, sizeof(line), pf) != NULL) {
            //printf("Line: %s\n",line);            
                printf("Line after: %s\n",line); 
                while(true){
                    int i=0;
                    char c=line[i]; //parse every char of the line
                            //assert(c==' ');
                            int locationS=0; //index in the name
                            int locationV=0; //index in the value
                            while((c==' ')&& i<sizeof(line)){
                                //look for next sequence of chars
                                ++i;
                                c=line[i];
                                if(c=='#'){
                                    break;
                                }
                            }
                            if(c=='#'){ break;}
                            assert(c!=' ');
                            while (c!=' '&&i<sizeof(line))
                            {
                                name[locationS]=c;
                                locationS++;
                                //printf("%d",locationS);
                                ++i;
                                c=line[i];if(c=='#'){
                                    break;
                                }
                            }
                            if(c=='#'){ break;}
                            assert(c==' ');
                            while(c==' '&&i<sizeof(line)){
                                //look for next sequence of chars
                                ++i;
                                c=line[i];
                                if(c=='#'){
                                    break;
                                }
                            }
                            if(c=='#'){ break;}
                            assert(c!=' ');
                            printf("\n");
                             while ((c!=' '&& c!='\n')&&i<sizeof(line))
                            {
                                value[locationV]=c;
                                locationV++;
                                ++i;
                                c=line[i];if(c=='#'){
                                    break;
                                }
                            }
                            printf("name: %s value: %s : \n",name, value);
                            memset(&name[0], 0, sizeof(name));
                            memset(&value[0], 0, sizeof(value));
                            break; //nothing interesting left
                }
        }
        fclose(pf);
    }else{
        printf("Error in file\n");
        exit(EXIT_FAILURE);
    }
}

1 Ответ

0 голосов
/ 04 ноября 2019

Паша, ты делаешь некоторые вещи правильно, но потом ты делаешь то, что пытаешься сделать, намного сложнее, чем нужно. Во-первых, избегайте использования в своем коде magic-numbers , например char name[10000];. Вместо этого:

...
#define MAXC 1024   /* if you need a constant, #define one (or more) */

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

    char line[MAXC];
    ...

(вы очень хорошо следовали правилу Не экономьте на размере буфера :)

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

    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

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

Теперь, когда файл открыт и проверен на то, что он открыт для чтения и обрабатывается любая ошибка, вы можете приступить к чтению каждой строки в вашем файлеЕсли вы не храните имена в массиве, который должен сохраняться в цикле чтения, вы можете просто объявить name[MAXC]; в блоке цикла чтения, например,

    while (fgets (line, MAXC, fp)) {    /* read each line of input */
        char name[MAXC];                /* storage for name */
        int val;                        /* integer value for val */

( note: вместо того, чтобы объявлять другой массив для хранения значения, мы просто объявили val как int и будем использовать sscanf для анализа name и val, преобразуя значение непосредственно в int в то время)

Каждый раз, когда вы используете линейно-ориентированную функцию ввода (например, fgets() или POSIX getline(), вы захотите обрезать чтение '\n' и включить его в заполненный буфер. Это легко сделать с помощью strcspn, см. strspn (3) - страница руководства Linux . Это простой одиночный вызов, в котором вы используете возврат от strcspn в качестве индекса для '\n', чтобы перезаписать '\n' с помощью нуль-заканчивающегося символа (то есть '\0', или просто 0)

        line[strcspn (line, "\n")] = 0; /* trim '\n' from end of line */

Теперь все, что вам нужно сделать, это проверить наличие первого '#' в line, который отмечает начало комментария. Если найден, вы просто перезапишете '#' с нуль-завершающим символом , как вы это сделали для '\n', например,

        line[strcspn (line, "#")] = 0;  /* overwrite '#' with nul-char */

Теперь, когда у вас есть строка и вы удалили'\n' и любой комментарий, который может присутствовать, вы можете проверить, что line не пустой (это означает, что он начинается с '#' или является просто пустой строкой, содержащей только '\n')

        if (!*line)                     /* if empty-string */
            continue;                   /* get next line */

( примечание: if (!*line) - это просто сокращение для if (line[0] == 0). Когда вы разыменовываете свой буфер, например, *line, вы просто возвращаете первый элемент (первый символ) как *line == *(line + 0) внотация указателя, эквивалентная *(line + 0) == line[0] в нотации индекса массива. [] также работает как разыменование.)

Теперь просто выполните синтаксический анализ name и val непосредственно из line, используяsscanf. Спецификаторы преобразования "%s" и "%d" будут игнорировать все начальные пробелы перед спецификатором преобразования. Вы можете использовать этот простой метод, если только name не содержит пробелов. Так же, как вы подтвердите возврат открытия вашего файла, вы подтвердите возврат из sscanf, чтобы определить, является ли числоуказанные вами преобразования успешно выполнены. Например:

        if (sscanf (line, "%1023s %d", name, &val) == 2)  /* have name/value? */
            printf ("\nline: %s\nname: %s\nval : %d\n", line, name, val);
        else
            printf ("\nline: %s (doesn't contain name/value\n", line);

( примечание: с помощью модификатора field-width для вашей строки, например, "%1023s" вы защищаете границы массива для name. Ширина поля ограничивает sscanf от записи более 1023 char + \0 в имя. Это не может быть предоставлено переменной или макросом и является одним из случаев, когда вы должны вставить магическое число в вашем коде ... Для каждого правила обычно есть одна или две оговорки ...)

Если вы запросили 2 преобразования, а sscanf вернули 2, то вы знаете, что оба запрошенныхпреобразования были успешными. Кроме того, поскольку для val вы указали integer преобразование, вам гарантировано, что значение будет содержать integer .

Это все, что нужно сделать. Все, что остается, - это закрыть файл (если он не читает из stdin), и все готово. Полный пример может быть:

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

#define MAXC 1024   /* if you need a constant, #define one (or more) */

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

    char line[MAXC];
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

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

    while (fgets (line, MAXC, fp)) {    /* read each line of input */
        char name[MAXC];                /* storage for name */
        int val;                        /* integer value for val */

        line[strcspn (line, "\n")] = 0; /* trim '\n' from end of line */
        line[strcspn (line, "#")] = 0;  /* overwrite '#' with nul-char */

        if (!*line)                     /* if empty-string */
            continue;                   /* get next line */

        if (sscanf (line, "%1023s %d", name, &val) == 2)  /* have name/value? */
            printf ("\nline: %s\nname: %s\nval : %d\n", line, name, val);
        else
            printf ("\nline: %s (doesn't contain name/value\n", line);
    }
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
}

( примечание: , если вы хотите распечатать необработанный line до обрезки '\n' и комментариев, просто переместите печать line перед вызовами на strcspn. Выше line печатается с указанием конечного состояния line перед вызовом sscanf)

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

Использование входного файла, хранящегося в dat/nameval.txtв моей системе вы могли бы просто сделать следующее, чтобы прочитать значения, перенаправленные с stdin:

$ ./bin/parsenameval <dat/nameval.txt

line: a 12
name: a
val : 12

line: b     33
name: b
val : 33

line: nice 6
name: nice
val : 6

( note: просто удалить перенаправление <, чтобы фактически открыть и прочитать изфайл вместо того, чтобы оболочка делала это для вас. Шесть к одному, полдюжины к другому.)

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...