fgets не может правильно читать, если в конце файла нет новой строки

Проблема в том, что fgets отображает неправильно, если в конце файла нет новой строки. Предположим, если у меня есть 2 текстовых файла, как показано ниже,

содержимое text1.txt:

German Hoy
43
68
Jesse Boster
88
29
  • обратите внимание, что файл полностью заканчивается после 29. после 29 строки нет.

содержимое text2.txt:

German Hoy
43
68
Jesse Boster
88
29
  • обратите внимание, что после 29 есть еще одна строка.

Моя проблема:

программа работает правильно для text2.txt, когда после конца файла есть еще одна строка. Однако это не так, если у меня нет еще одной строки в конце файла, например text1.txt. Как я могу это исправить? Я хочу иметь тот же результат независимо от наличия или отсутствия еще одной строки в конце файла. (В обоих случаях он должен выводить одинаковый результат)

Ниже показана часть исходного кода, относящаяся к проблеме:

while( fgets (s, 60, file)!=NULL )  {
        s[strlen(s)-1] = '\0';
        strcpy(tempName, s);

        fgets(s, 60, file);
        s[strlen(s)-1] = '\0';
        sscanf(s, "%d", &tempMid);

        fgets(s, 60, file);
        s[strlen(s)-1] = '\0';
        sscanf(s, "%d", &tempFinal);

        setup(tempName, tempMid, tempFinal);
    }

Используется система LINUX


person online.0227    schedule 04.03.2017    source источник
comment
Какой результат вы получаете? Вы должны проверять каждый из вызовов fgets(), чтобы убедиться, что вы не получили отчет EOF. Вы должны показать нам MCVE (минимальный воспроизводимый пример) — например, ваш вызов setup() можно заменить на подходящий printf() звонок. Кроме того, на какой платформе вы работаете? Windows против Unix-подобных может иметь значение.   -  person Jonathan Leffler    schedule 05.03.2017
comment
s[strlen(s)-1] = '\0'; заменить на s[strcspn(s, "\n")] = '\0';. Также второй и третий s[strlen(s)-1] = '\0'; не нужны.   -  person BLUEPIXY    schedule 05.03.2017
comment
Спасибо за обновление, за исключением того, что обновление не является MCVE, а ввод полностью отличается от того, что было описано ранее, а вывод, который вы получаете, отсутствует, а ожидаемый вывод не очевиден из ввода. Вы показываете. На самом деле, обновление не помогает вообще.   -  person Jonathan Leffler    schedule 05.03.2017


Ответы (3)


Вы можете поместить дополнительную новую строку в конец буфера s независимо от того,

fgets(s, 60, file);
length = strlen(s);
s[length] = '\n';
s[length+1] = '\0';
sscanf(s, "%d", &tempFinal);

ВАЖНОЕ ПРИМЕЧАНИЕ:

Вы должны убедиться, что ваш буфер имеет длину не менее 61 байта, чтобы соответствовать новой строке.

person Ameen    schedule 04.03.2017
comment
В конце уже был нулевой байт; вы добавляете только один символ, поэтому вам нужен только 61 байт в буфере. - person Jonathan Leffler; 05.03.2017
comment
Вы правы, я думал, что fgets будет похож на strcpy :) - person Ameen; 05.03.2017
comment
@ameen Да, теперь исправлено, большое-пре-большое спасибо! - person online.0227; 05.03.2017
comment
Почему бы просто не опустить 3 строки length = strlen(s); s[length] = '\n'; s[length+1] = '\0';? - person chux - Reinstate Monica; 05.03.2017
comment
потому что проблема в том, что в конце файла нет новой строки - person Ameen; 05.03.2017
comment
@ameen Это не функциональная разница с кодом этого ответа, если 3 строки были пропущены при попытке найти tempFinal. - person chux - Reinstate Monica; 05.03.2017
comment
@ameen еще один вопрос. Мне нужно изменить fgets(s, 60, file); в fgets(s, 61, файл); , потому что вы сказали, что мой буфер имеет длину не менее 61 байта? - person online.0227; 05.03.2017
comment
@ online.0227 нет, просто сделайте размер буфера 61 и оставьте fgets как есть, на один байт меньше. - person Ameen; 05.03.2017
comment
@ameen о, хорошо, поэтому я меняю s[60] на s[61], но не меняю fgets. большое тебе спасибо. - person online.0227; 05.03.2017
comment
@chux хм..., теперь я думаю об этом, это может сработать, он мог перезаписать последнюю цифру и получить неправильный результат, я подумал, что sscanf не работает, так как он не указал, в чем проблема, и так как я никогда больше не использую стандартную библиотеку, потому что у меня есть своя, и я не помню ее подробностей. поэтому, если это не работает для него, когда нет новой строки, добавление новой строки заставит ее работать наверняка. - person Ameen; 05.03.2017
comment
Обратите внимание, что strcat(s,"\n"); будет делать то же самое, что и length = strlen(s); s[length] = '\n'; s[length+1] = '\0';. - person chux - Reinstate Monica; 05.03.2017

Удалите новую строку по желанию:

while( fgets (s, 60, file)!=NULL )  {
  s[strcspn(s, "\n")] = '\0';

Не используйте s[strlen(s)-1] = '\0';, так как это может быть хакерской уязвимостью. fgets() читает нулевой символ так же, как и любой другой неновый символ строки. Этот первый символ в строке может быть '\0', а затем код OP вызывает неопределенное поведение.

Кроме того, в коде OP отсечение потенциального символа новой строки даже не требуется.

    fgets(s, 60, file);
    // s[strlen(s)-1] = '\0';
    sscanf(s, "%d", &tempMid);

Также было бы лучше проверить возвращаемое значение sccanf() или, возможно, использовать strtol().

person chux - Reinstate Monica    schedule 04.03.2017

Этот код примерно соответствует MCVE:

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

int main(void)
{
    char s[60];
    char tempName[60];
    int tempMid;
    int tempFinal;
    FILE *file = stdin;

    while (fgets(s, 60, file) != NULL)
    {
        s[strlen(s) - 1] = '\0';
        strcpy(tempName, s);

        if (fgets(s, 60, file) == NULL)
            break;
        s[strlen(s) - 1] = '\0';
        sscanf(s, "%d", &tempMid);

        if (fgets(s, 60, file) == NULL)
            break;
        s[strlen(s) - 1] = '\0';
        sscanf(s, "%d", &tempFinal);

        printf("[%s] %d %d\n", tempName, tempMid, tempFinal);
    }

    return 0;
}

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

Я назвал программу fg17. При запуске ваших двух файлов данных я получаю:

$ fg17 < text1.txt
[German Hoy] 43 68
[Jesse Boster] 88 2
$ fg17 <text2.txt
[German Hoy] 43 68
[Jesse Boster] 88 29
$

Это то, что я ожидал, поскольку ваш код удаляет последний символ последней строки, независимо от того, является ли это новой строкой или нет. Если вы хотите, чтобы вывод включал 9 из 29, вам нужно быть более осторожным:

s[strcspn(s, "\n")] = '\0';

Когда это изменение развернуто 3 раза, результат будет одинаковым для обеих программ.

Какая разница? strcspn() возвращает количество символов, не найденных в строковом аргументе, или количество символов до нулевого байта. Если новой строки нет, он сообщает о нулевом байте, и присваивание перезаписывает нулевой байт другим нулевым байтом — без операции. Зато надежный и безопасный.

person Jonathan Leffler    schedule 04.03.2017