fgets() не работает после fscanf()

Я использую fscanf для чтения даты, а затем fgets для чтения заметки. Однако после первой итерации fscanf возвращает значение -1.

Я использовал GDB для пошаговой отладки программы. Он отлично работает до первого использования fgets. Когда я пытаюсь распечатать строку, прочитанную fgets на первой итерации, это дает мне следующее:

(gdb) print line
$6 = "\rtest\r18/04/2010\rtest2\r03/05/2010\rtest3\r05/08/2009\rtest4\r\n\000\000\000\000q\352\261\a\370\366\377\267.N=\366\000\000\000\000\003\000\000\000\370xC\000\000\000\000\000\000\000\000\000\001\000\000\000\227\b\000\000\070\367\377\267H\364\377\267\362\202\004\bdoD\000\354\201\004\b\001\000\000\000\304oC\000p\363\377\277\260zC\000D\363\377\277\n!B\000\064\363\377\277\354\201\004\b(\363\377\277TzC\000\000\000\000\000\070\367\377\267\001\000\000\000\000\000\000\000\001\000\000\000\370xC\000\001\000\000\000\000\000\312\000\000\000\000\000\377\260\360\000\001\000\000\000\277\000\000\000\364\317\000\000\344\261\\\000\000\000\000\000p\363\377\277|\233\004\b\350\362\377\277 \204\004\b\005\000\000\000|\233\004\b\030\363\377\277"

Похоже, что fgets читает оставшиеся записи, а затем сохраняет их все в одной строке.

Я не уверен, почему он это делает.

Вот основной код:

int main(int argc, char* argv[]) {
    FILE* file;
    int numEntries, i = 0;
    int index = atoi(argv[1]);
    char line[SIZE];
    JournalEntry *entry;

    /*argument provided is the entry user wants to be displayed*/
    if (argc > 2) {
        perror("Error: Too many arguments provided");
    }
    file = fopen("journalentries.txt", "r");
    if (file == NULL) {
        perror("Error in opening file");
    }

    if (fscanf(file, "%d", &numEntries) != 1) {
        perror("Unable to read number of entries");
    }

    entry = (JournalEntry*)malloc(numEntries  * sizeof(JournalEntry));
    if (entry == NULL) {
        perror("Malloc failed");
    }

    for (i = 0; i < numEntries; i++) {
        if (fscanf(file, "%d/%d/%d", &entry[i].day, &entry[i].month, &entry[i].year) != 3) {
            perror("Unable to read date of entry");
        }

        if (fgets(line, sizeof(line), file) == NULL) {
            perror("Unable to read text of entry");
        }
    }

    printf("%d-%02d-%02d %s: ", entry[index].year, entry[index].month, entry[index].day, entry[index].text);

    if(ferror(file)) {
        perror("Error with file");
    }

    fclose(file);
    free(entry);

    return 0;
}

Файл, который я должен прочитать: самая первая строка содержит количество записей, которые нужно прочитать

4
12/04/2010
test
18/04/2010
test2
03/05/2010
test3
05/08/2009
test4

Структура JournalEntry, расположенная в заголовочном файле:

typedef struct {
    int day;
    int month;
    int year;
    char text[250];
} JournalEntry;

person kaozgamer    schedule 14.10.2013    source источник
comment
Откуда берется входной файл с использованием \r в качестве конца строки? Концы строк должны быть \n.   -  person alk    schedule 14.10.2013
comment
Я создал входной файл с помощью notepad++   -  person kaozgamer    schedule 14.10.2013
comment
@kaozgamer попробуй с numEntries -1   -  person Umer Farooq    schedule 14.10.2013
comment
+1 за проверку результатов fscanf().   -  person chux - Reinstate Monica    schedule 14.10.2013


Ответы (3)


Похоже, что fgets читает оставшиеся записи, а затем сохраняет их все в одной строке.

Да, '\r' не является терминатором строки. Поэтому, когда fscanf прекращает синтаксический анализ на первом недопустимом символе и оставляет их в буфере, тогда fgets будет читать их до конца строки. И поскольку в файле нет допустимых разделителей строк, то есть до конца файла.

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

Кроме того, вам нужна двойная проверка возвращаемого значения fscanf. Используйте perror только в том случае, если возвращаемое значение равно -1, иначе сообщение об ошибке вообще не будет связано с ошибкой. Если возвращаемое значение равно >=0, но отличается от того, что вы хотели, то напечатайте пользовательское сообщение об ошибке «недопустимый синтаксис ввода» или что-то еще (и, возможно, используйте fgets для чтения остальной части строки из буфера).

Кроме того, чтобы надежно смешать scanf и fgets, вам нужно добавить пробел в строку формата fscanf, чтобы он считывал любые пробелы в конце строки (также в начале следующей строки и любые пустые строки, поэтому будьте осторожны если это важно) вот так:

int items_read = scanf("%d ", &intvalue);

Как указано в другом ответе, вероятно, лучше всего читать строки только с fgets, а затем анализировать их с помощью sscanf построчно.

person hyde    schedule 14.10.2013
comment
Я воссоздал входной файл в gvim. Добавление пробела в строку формата fscanf помогло. Спасибо! - person kaozgamer; 14.10.2013

Не смешивайте fscanf() и fgets(), так как первый может оставить что-то в буфере потока.

Для линейно-ориентированного формата читайте только полные строки, используя fgets(), затем используйте, например. sscanf() для разбора прочитанного.

person unwind    schedule 14.10.2013

Строка, которую вы видите при запуске GDB, действительно заканчивается первым нулевым символом:

"\rtest\r18/04/2010\rtest2\r03/05/2010\rtest3\r05/08/2009\rtest4\r\n\000"

Остальные данные после игнорируются (при использовании обычных str-функций);

person Atle    schedule 14.10.2013