Проблемы с чтением строки с помощью fscanf ()

Я пытаюсь прочитать строку, используя следующий код:

while(fscanf(f, "%[^\n\r]s", cLine) != EOF )
{
    /* do something with cLine */
}

Но почему-то каждый раз я получаю только первую строчку. Это плохой способ читать строку? Что я должен исправить, чтобы он работал должным образом?


person Community    schedule 14.05.2009    source источник


Ответы (7)


Практически всегда использовать функцию fscanf() - плохая идея, поскольку в случае сбоя она может оставить указатель файла в неизвестном месте.

Я предпочитаю использовать fgets() для ввода каждой строки, а затем sscanf() это. Затем вы можете продолжить изучение прочитанной строки по своему усмотрению. Что-то вроде:

#define LINESZ 1024
char buff[LINESZ];
FILE *fin = fopen ("infile.txt", "r");
if (fin != NULL) {
    while (fgets (buff, LINESZ, fin)) {
        /* Process buff here. */
    }
    fclose (fin);
}

fgets() похоже на то, что вы пытаетесь сделать, читая строку, пока не встретите символ новой строки.

person paxdiablo    schedule 14.05.2009
comment
Как я могу использовать функцию sscanf для чтения только строки (BTY составляет 1024 размера строки?) Спасибо! - person ; 14.05.2009
comment
fgets читает одну строку или меньше. fgets (buffer, 1024, file) будет читать строку, сколько есть в файле, или 1024 символа. Если вы читаете целую строку, тогда buffer [strlen (buffer)] == '\ n'. Если вы достигнете EOF, он вернет null, в противном случае в строке будет больше текста. - person Tordek; 14.05.2009
comment
stackoverflow.com/questions/865335/ - person Rob Kam; 15.05.2009

Если вы хотите читать файл построчно (здесь разделитель строк == '\ n'), просто сделайте это:

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

int main(int argc, char **argv)
{
        FILE *fp;
        char *buffer;
        int ret;

        // Open a file ("test.txt")
        if ((fp = fopen("test.txt", "r")) == NULL) {
                fprintf(stdout, "Error: Can't open file !\n");
                return -1;
        }
        // Alloc buffer size (Set your max line size)
        buffer = malloc(sizeof(char) * 4096);
        while(!feof(fp))
        {
                // Clean buffer
                memset(buffer, 0, 4096);
                // Read a line
                ret = fscanf(fp, "%4095[^\n]\n", buffer);
                if (ret != EOF) {
                        // Print line
                        fprintf(stdout, "%s\n", buffer);
                }
        }
        // Free buffer
        free(buffer);
        // Close file
        fclose(fp);
        return 0;
}

Наслаждаться :)

person Community    schedule 15.06.2011

Если вы попробуете while( fscanf( f, "%27[^\n\r]", cLine ) == 1 ), возможно, вам повезет немного больше. Три отличия от вашего оригинала:

  • ограничение длины того, что читается - я использовал 27 здесь в качестве примера, и, к сожалению, семейство scanf() требует ширины поля буквально в строке формата и не может использовать механизм *, который printf() может для передачи значения в
  • избавиться от s в строке формата - %[ - это спецификатор формата для «всех символов, совпадающих или не совпадающих с набором», и набор завершается ] сам по себе
  • сравните возвращаемое значение с ожидаемым количеством конверсий (и для простоты управления убедитесь, что это число равно 1)

Тем не менее, вы получите тот же результат с меньшими усилиями, используя fgets() для чтения столько строки, сколько поместится в вашем буфере.

person mlp    schedule 14.05.2009
comment
Это все равно оставит его с исходной проблемой чтения только первой строки. Лучше было бы% 27 [^ \ n \ r]% * [\ n \ r], чтобы использовался несоответствующий символ. - person Dingo; 15.05.2009

Использование fscanf для чтения / токенизации файла всегда приводит к хрупкому коду или боли и страданиям. Чтение строки и разметка или сканирование этой строки безопасны и эффективны. Ему нужно больше строк кода - а это значит, что нужно больше времени, чтобы ДУМАТЬ о том, что вы хотите сделать (и вам нужно обрабатывать конечный размер входного буфера), - но после этого жизнь просто воняет меньше.

Не борись с fscanf. Просто не используйте это. Всегда.

person ashleigh    schedule 08.12.2009

Мне кажется, вы пытаетесь использовать операторы регулярных выражений в своей строке fscanf. Строка [^\n\r] ничего не значит для fscanf, поэтому ваш код работает не так, как ожидалось.

Кроме того, fscanf () не возвращает EOF, если элемент не совпадает. Скорее, он возвращает целое число, которое указывает количество совпадений, которое в вашем случае, вероятно, равно нулю. EOF возвращается только в конце потока или в случае ошибки. Итак, что происходит в вашем случае, так это то, что первый вызов fscanf () читает полностью до конца файла в поисках совпадающей строки, а затем возвращает 0, чтобы вы знали, что совпадения не найдено. Второй вызов затем возвращает EOF, потому что был прочитан весь файл.

Наконец, обратите внимание, что оператор формата% s scanf захватывает только следующий пробельный символ, поэтому вам ни в коем случае не нужно исключать \ n или \ r.

Дополнительную информацию см. В документации fscanf: http://www.cplusplus.com/reference/clibrary/cstdio/fscanf/

person JSBձոգչ    schedule 14.05.2009
comment
[^ a-z] действительно исключает a-z в scanf. Хотя указанная выше строка ищет пару символов, первый из которых не является разрывом строки, а второй - s - person Tordek; 14.05.2009
comment
Документация по fscanf на cplusplus.com неполная. Google "fscanf scanset". - person Dingo; 15.05.2009

У вашего цикла есть несколько проблем. Вы написали:

while( fscanf( f, "%[^\n\r]s", cLine ) != EOF ) 
    /* do something */;

Некоторые моменты, которые следует учитывать:

  1. fscanf () возвращает количество сохраненных элементов. Он может вернуть EOF, если он читает после конца файла или если дескриптор файла содержит ошибку. Вам нужно отличать действительный возврат нуля, когда в буфере cLine нет нового содержимого, от успешного чтения.

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

  3. Написанный вами шаблон, вероятно, не соответствует вашим задумкам. Он сопоставляет любое количество символов, не являющихся CR или LF, а затем ожидает найти литерал s.

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

  5. Если вы специально не запросили, чтобы f был открыт в двоичном режиме, перевод концов строки будет происходить в библиотеке, и вы, как правило, никогда не увидите символы CR, и обычно не в текстовых файлах.

Вероятно, вам нужен цикл, похожий на следующий:

while(fgets(cLine, N_CLINE, f)) {
    /* do something */ ;
}

где N_CLINE - количество байтов, доступных в буфере, начиная с cLine.

Функция fgets() - наиболее предпочтительный способ чтения строки из файла. Его второй параметр - это размер буфера, и он читает из файла в буфер на 1 байт меньше этого размера. Он всегда завершает буфер символом nul, чтобы его можно было безопасно передать другим строковым функциям C.

Он останавливается при чтении первого из конца файла, новой строки или buffer_size-1 байтов.

Он оставляет символ новой строки в буфере, и этот факт позволяет вам отличить одну строку длиннее, чем ваш буфер, от строки, короче, чем буфер.

Он возвращает NULL, если байты не были скопированы из-за конца файла или ошибки, а в противном случае - указатель на буфер. Вы можете использовать feof() и / или ferror(), чтобы различать эти случаи.

person RBerteig    schedule 14.05.2009
comment
спасибо, я так и сделал, но мне интересно, что, если моя линия больше, чем размер, который я установил, будет ли она вырезать часть следующей строки или может вызвать какие-либо другие проблемы - person ; 14.05.2009
comment
Если строка ввода длиннее, чем буфер, переданный в fgets (), она прекратит чтение до конца строки ввода и выдаст вам то, что она прочитала до сих пор в буфере. Вы знаете, что это произошло, потому что в конце буфера нет \ n. Каждый вызов fgets () будет продолжать чтение, поэтому вы можете обрабатывать длинную строку в буфере за раз, выполняя цикл до тех пор, пока буфер не закончится на \ n. Единственная проблема - это разумный синтаксический анализ вашего ввода, когда он сломан в произвольном месте. - person RBerteig; 15.05.2009

Я думаю, что проблема с этим кодом заключается в том, что когда вы читаете с помощью% [^ \ n \ r] s, на самом деле вы читаете до тех пор, пока не достигнете '\ n' или '\ r', но вы не читаете '\ n 'или' \ r 'тоже. Таким образом, вам нужно получить этот символ, прежде чем вы снова будете читать с помощью fscanf в цикле. Сделайте что-нибудь вроде этого:

do{
    fscanf(f, "%[^\n\r]s", cLine) != EOF

    /* Do something here */

}while(fgetc(file) != EOF)
person Eduardo Dalapicola    schedule 30.04.2017