Паша, ты делаешь некоторые вещи правильно, но потом ты делаешь то, что пытаешься сделать, намного сложнее, чем нужно. Во-первых, избегайте использования в своем коде 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: просто удалить перенаправление <
, чтобы фактически открыть и прочитать изфайл вместо того, чтобы оболочка делала это для вас. Шесть к одному, полдюжины к другому.)
Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы. Если по какой-то причине вы не можете использовать какую-либо функцию, чтобы помочь вам разобрать строку и должны использовать только указатели или индексирование массива, дайте мне знать. Следуя вышеприведенному подходу, требуется лишь немного усилий, чтобы заменить каждую из операций ее эквивалентом вручную.