Получение std :: ifstream для обработки LF, CR и CRLF?


85

Конкретно меня интересует istream& getline ( istream& is, string& str );. Есть ли у конструктора ifstream возможность указать ему преобразовывать все кодировки новой строки в '\ n' под капотом? Я хочу иметь возможность вызывать getlineи изящно обрабатывать все окончания строк.

Обновление : чтобы уточнить, я хочу иметь возможность писать код, который компилируется практически где угодно и будет вводить данные практически из любого места. Включая редкие файлы, в которых '\ r' без '\ n'. Сведение к минимуму неудобств для пользователей программного обеспечения.

Эту проблему легко решить, но мне все еще любопытно, как в стандарте гибко обрабатывать все форматы текстовых файлов.

getlineчитает целую строку до '\ n' в строку. '\ N' потребляется из потока, но getline не включает его в строку. Пока это нормально, но может быть '\ r' непосредственно перед '\ n', который включается в строку.

В текстовых файлах есть три типа окончаний строк : '\ n' - это обычное окончание на машинах Unix, '\ r' (я думаю) использовалось в старых операционных системах Mac, а Windows использует пару, '\ r' после "\ n".

Проблема в том, что getlineв конце строки остается '\ r'.

ifstream f("a_text_file_of_unknown_origin");
string line;
getline(f, line);
if(!f.fail()) { // a non-empty line was read
   // BUT, there might be an '\r' at the end now.
}

Изменить Спасибо Нилу за то, что он указал, что f.good()это не то, что я хотел. !f.fail()это то, что я хочу.

Я могу удалить его вручную (см. Редактирование этого вопроса), что легко для текстовых файлов Windows. Но меня беспокоит, что кто-то загрузит файл, содержащий только '\ r'. В этом случае я предполагаю, что getline будет использовать весь файл, думая, что это одна строка!

.. и это даже без Юникода :-)

.. может быть, у Boost есть хороший способ использовать по одной строке из любого типа текстового файла?

Редактировать Я использую это для обработки файлов Windows, но я все еще чувствую, что не должен! И это не будет развиваться для файлов, содержащих только '\ r'.

if(!line.empty() && *line.rbegin() == '\r') {
    line.erase( line.length()-1, 1);
}

2
\ n означает новую строку любым способом, который представлен в текущей ОС. Об этом позаботится библиотека. Но для этого программа, скомпилированная в Windows, должна читать текстовые файлы из Windows, программа, скомпилированная в unix, текстовые файлы из unix и т. Д.
Джордж Кастринис

1
@George, хотя я компилирую на машине с Linux, иногда я использую текстовые файлы, изначально пришедшие с машины Windows. Я мог бы выпустить свое программное обеспечение (небольшой инструмент для сетевого анализа), и я хочу иметь возможность сообщать пользователям, что они могут загружать текстовый файл (ASCII-подобный) практически в любое время.
Аарон МакДэйд


1
Обратите внимание, что if (f.good ()) не делает то, что вы думаете.

1
@JonathanMee: Это может быть, как это . Может быть.
Гонки за легкостью на орбите

Ответы:


111

Как указал Нил, «среда выполнения C ++ должна правильно работать с любым соглашением о завершении строки для вашей конкретной платформы».

Однако люди перемещают текстовые файлы между разными платформами, так что этого недостаточно. Вот функция, которая обрабатывает все три окончания строк («\ r», «\ n» и «\ r \ n»):

std::istream& safeGetline(std::istream& is, std::string& t)
{
    t.clear();

    // The characters in the stream are read one-by-one using a std::streambuf.
    // That is faster than reading them one-by-one using the std::istream.
    // Code that uses streambuf this way must be guarded by a sentry object.
    // The sentry object performs various tasks,
    // such as thread synchronization and updating the stream state.

    std::istream::sentry se(is, true);
    std::streambuf* sb = is.rdbuf();

    for(;;) {
        int c = sb->sbumpc();
        switch (c) {
        case '\n':
            return is;
        case '\r':
            if(sb->sgetc() == '\n')
                sb->sbumpc();
            return is;
        case std::streambuf::traits_type::eof():
            // Also handle the case when the last line has no line ending
            if(t.empty())
                is.setstate(std::ios::eofbit);
            return is;
        default:
            t += (char)c;
        }
    }
}

А вот и тестовая программа:

int main()
{
    std::string path = ...  // insert path to test file here

    std::ifstream ifs(path.c_str());
    if(!ifs) {
        std::cout << "Failed to open the file." << std::endl;
        return EXIT_FAILURE;
    }

    int n = 0;
    std::string t;
    while(!safeGetline(ifs, t).eof())
        ++n;
    std::cout << "The file contains " << n << " lines." << std::endl;
    return EXIT_SUCCESS;
}

1
@Miek: Я обновил код после предложения Bo Persons stackoverflow.com/questions/9188126/… и провел несколько тестов. Теперь все работает как надо.
Johan Råde

1
@Thomas Weller: Конструктор и деструктор для часового выполнены. Они выполняют такие действия, как синхронизация потоков, пропуск пробелов и обновление состояния потока.
Johan Råde

1
В случае EOF, какова цель проверки того, что tон пуст перед установкой eofbit. Разве этот бит не следует устанавливать независимо от того, были ли прочитаны другие символы?
Yay295,

1
Yay295: Флаг eof должен быть установлен не тогда, когда вы дойдете до конца последней строки, а когда вы попытаетесь прочитать за пределами последней строки. Проверка гарантирует, что это произойдет, когда последняя строка не имеет конца строки. (Попробуйте удалить проверку, а затем запустите тестовую программу для текстового файла, где в последней строке нет EOL, и вы увидите.)
Йохан Роде, 02

3
Это также читает пустую последнюю строку, которая не поведение , std::get_lineкоторое игнорирует пустую последнюю строку. Я использовал следующий код в случае eof для имитации std::get_lineповедения:is.setstate(std::ios::eofbit); if (t.empty()) is.setstate(std::ios::badbit); return is;
Патрик Рукс

11

Среда выполнения C ++ должна правильно работать с любым соглашением о конце строки для вашей конкретной платформы. В частности, этот код должен работать на всех платформах:

#include <string>
#include <iostream>
using namespace std;

int main() {
    string line;
    while( getline( cin, line ) ) {
        cout << line << endl;
    }
}

Конечно, если вы имеете дело с файлами с другой платформы, все ставки отключены.

Поскольку две наиболее распространенные платформы (Linux и Windows) обе завершают строки символом новой строки, а Windows предшествует ему возвратом каретки, вы можете проверить последний символ lineстроки в приведенном выше коде, чтобы увидеть, является ли он\r и если да удалите его перед обработкой для конкретного приложения.

Например, вы можете предоставить себе функцию стиля getline, которая выглядит примерно так (не проверено, использование индексов, substr и т. Д. Только в педагогических целях):

ostream & safegetline( ostream & os, string & line ) {
    string myline;
    if ( getline( os, myline ) ) {
       if ( myline.size() && myline[myline.size()-1] == '\r' ) {
           line = myline.substr( 0, myline.size() - 1 );
       }
       else {
           line = myline;
       }
    }
    return os;
}

9
Вопрос заключается в том о том , как работать с файлами из другой платформы.
Гонки легкости на орбите

4
@ Нил, этого ответа пока недостаточно. Если бы я только хотел обрабатывать CRLF, я бы не пришел в StackOverflow. Настоящая проблема - обрабатывать файлы, в которых есть только '\ r'. Сейчас они довольно редки, теперь, когда MacOS приблизилась к Unix, но я не хочу предполагать, что они никогда не будут загружены в мое программное обеспечение.
Аарон МакДэйд

1
@Aaron ну, если вы хотите иметь возможность обрабатывать ВСЕ, вам нужно написать свой собственный код для этого.

4
В своем вопросе я с самого начала ясно дал понять, что это легко обойти, подразумевая, что я хочу и могу это сделать. Я спросил об этом, потому что это, кажется, очень распространенный вопрос, и существует множество форматов текстовых файлов. Я предполагал / надеялся, что комитет по стандартам C ++ встроил это. Это был мой вопрос.
Аарон МакДэйд

1
@ Нил, я думаю, что я забыл еще об одной проблеме. Но сначала я согласен с тем, что для меня практично определить небольшое количество поддерживаемых форматов. Поэтому мне нужен код, который будет компилироваться в Windows и Linux и будет работать с любым форматом. Ваш safegetline- важная часть решения. Но если эта программа компилируется в Windows, нужно ли мне также открывать файл в двоичном формате? Компиляторы Windows (в текстовом режиме) позволяют '\ n' вести себя как '\ r' '\ n'? ifstream f("f.txt", ios_base :: binary | ios_base::in );
Аарон МакДэйд

8

Вы читаете файл в двоичном или в текстовом режиме? В режиме ТЕКСТ пара возврат каретки / перевод строки, CRLF , интерпретируется как ТЕКСТ конца строки или символа конца строки, но в ДВОИЧНОМ вы получаете только ОДИН байт за раз, что означает, что любой символ ДОЛЖЕНигнорироваться и оставляться в буфере для извлечения как другого байта! Возврат каретки в пишущей машинке означает, что тележка пишущей машинки, в которой находится печатающий рычаг, достигла правого края бумаги и возвращается к левому краю. Это очень механическая модель механической пишущей машинки. Тогда перевод строки означает, что рулон бумаги немного повернут вверх, чтобы бумага оказалась в положении, чтобы начать новую строку набора. Насколько я помню, одна из младших цифр в ASCII означает переход на один символ вправо без ввода, мертвый символ и, конечно, \ b означает возврат: переместить автомобиль на один символ назад. Таким образом, вы можете добавлять специальные эффекты, такие как нижний (введите подчеркивание), зачеркивание (введите минус), приблизительные различные акценты, отмените (введите X), без необходимости использования расширенной клавиатуры, просто отрегулировав положение автомобиля вдоль линии перед вводом строки. Таким образом, вы можете использовать напряжение ASCII размером в байты для автоматического управления пишущей машинкой без компьютера. Когда появляется автоматическая пишущая машинка,АВТОМАТИЧЕСКИЙ означает, что как только вы дойдете до самого дальнего края бумаги, вагон вернется влево и будет применен перевод строки, то есть предполагается, что вагон возвращается автоматически по мере продвижения рулона вверх! Таким образом, вам не нужны оба управляющих символа, только один, \ n, новая строка или перевод строки.

Это не имеет ничего общего с программированием, но ASCII старше и ЭЙ! похоже, что некоторые люди не думали, когда начали писать тексты! Платформа UNIX предполагает электрическую автоматическую печатную машину; модель Windows более полная и позволяет управлять механическими машинами, хотя некоторые управляющие символы становятся все менее и менее полезными в компьютерах, например, колокольчик, 0x07, если я хорошо помню ... Некоторые забытые тексты, должно быть, изначально были записаны с помощью управляющих символов для пишущих машинок с электрическим управлением, и это увековечило модель ...

На самом деле правильным вариантом было бы просто включить \ r, перевод строки, без необходимости возврата каретки, то есть автоматический, следовательно:

char c;
ifstream is;
is.open("",ios::binary);
...
is.getline(buffer, bufsize, '\r');

//ignore following \n or restore the buffer data
if ((c=is.get())!='\n') is.rdbuf()->sputbackc(c);
...

будет наиболее правильным способом обработки всех типов файлов. Однако обратите внимание, что \ n в режиме ТЕКСТ на самом деле является парой байтов 0x0d 0x0a, но 0x0d ЕСТЬ просто \ r: \ n включает \ r в режиме ТЕКСТ, но не в ДВОИЧНОМ , поэтому \ n и \ r \ n эквивалентны ... или должно быть. На самом деле это очень простая отраслевая путаница, типичная отраслевая инерция, поскольку принято говорить о CRLF на ВСЕХ платформах, а затем они попадают в различные двоичные интерпретации. Строго говоря, файлы, включающие ТОЛЬКО 0x0d (возврат каретки) как \ n (CRLF или перевод строки), искажаются в ТЕКСТЕ.режим (пишущая машинка: просто верните машину и зачеркните все ...) и представляют собой двоичный формат, не ориентированный на строку (либо \ r, либо \ r \ n, что означает строчную ориентацию), поэтому вы не должны читать как текст! Код должен выйти из строя, возможно, с каким-то пользовательским сообщением. Это зависит не только от ОС, но и от реализации библиотеки C, что усугубляет путаницу и возможные варианты ... (особенно для прозрачных слоев перевода UNICODE, добавляя еще одну точку артикуляции для сбивающих с толку вариантов).

Проблема с предыдущим фрагментом кода (механическая пишущая машинка) заключается в том, что он очень неэффективен, если после \ r (текста автоматической пишущей машинки) нет символов \ n. Затем он также принимает режим BINARY, в котором библиотека C вынуждена игнорировать текстовые интерпретации (языковой стандарт) и отдавать чистые байты. Не должно быть никакой разницы в фактических текстовых символах между обоими режимами, только в управляющих символах, поэтому, вообще говоря, чтение BINARY лучше, чем режим TEXT . Это решение эффективно для BINARYрежим типичных текстовых файлов ОС Windows независимо от вариантов библиотеки C и неэффективен для текстовых форматов других платформ (включая веб-переводы в текст). Если вы заботитесь об эффективности, лучше всего использовать указатель на функцию, протестировать элементы управления строкой \ r vs \ r \ n, как вам нравится, затем выбрать лучший пользовательский код getline в указателе и вызвать его из Это.

Между прочим, я помню, что нашел и несколько текстовых файлов \ r \ r \ n ... которые переводятся в двухстрочный текст, как это все еще требуется некоторым потребителям печатного текста.


+1 для «ios :: binary» - иногда вы действительно хотите прочитать файл как есть (например, для вычисления контрольной суммы и т. Д.) Без изменения во время выполнения окончания строк.
Matthias

2

Одним из решений было бы сначала выполнить поиск и заменить все окончания строк на '\ n' - точно так же, как, например, Git по умолчанию.


1

Вам не повезло, кроме написания собственного обработчика или использования внешней библиотеки. Проще всего проверить, чтобыline[line.length() - 1] это не \ r. В Linux это излишне, поскольку большинство строк оканчиваются символом '\ n', что означает, что вы потеряете немало времени, если это будет в цикле. В Windows это тоже лишнее. Однако как насчет классических файлов Mac, заканчивающихся на \ r? std :: getline не будет работать для этих файлов в Linux или Windows, потому что '\ n' и '\ r' '\ n' оканчиваются на '\ n', что устраняет необходимость проверять наличие '\ r'. Очевидно, что такая задача, которая работает с этими файлами, не будет работать. Конечно, существуют многочисленные системы EBCDIC, с чем большинство библиотек не решится взяться.

Проверка на "\ r", вероятно, лучшее решение вашей проблемы. Чтение в двоичном режиме позволит вам проверить все три общих окончания строк ('\ r', '\ r \ n' и '\ n'). Если вас интересуют только Linux и Windows, поскольку окончания строк Mac в старом стиле не должны существовать надолго, проверьте наличие только '\ n' и удалите завершающий символ '\ r'.


0

Если известно, сколько элементов / номеров в каждой строке, можно прочитать одну строку, например, с 4 цифрами как

string num;
is >> num >> num >> num >> num;

Это также работает с окончаниями других строк.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.