Отримання std :: ifstream для обробки LF, CR та CRLF?


85

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

Оновлення : Щоб уточнити, я хочу мати можливість писати код, який компілюється майже в будь-якому місці і прийматиме введення майже з будь-якого місця. Включаючи рідкісні файли, у яких є "\ r" без "\ n". Зменшення незручностей для будь-яких користувачів програмного забезпечення.

Вирішити проблему легко, але мені все ще цікаво, як правильно, як правило, гнучко обробляти всі формати текстових файлів.

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

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

Проблема в тому, що 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 буде споживати весь файл, думаючи, що це один рядок!

.. і це навіть не враховуючи Unicode :-)

.. можливо 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
Зверніть увагу, що якщо (f.good ()) не робить того, що, на вашу думку, робить.

1
@JonathanMee: Це може бути, як це . Можливо.
Гонки легкості на орбіті

Відповіді:


111

Як зазначив Ніл, "час виконання С ++ повинен коректно обробляти будь-яку конвенцію про закінчення рядка для вашої конкретної платформи".

Однак люди переносять текстові файли між різними платформами, тож це недостатньо добре. Ось функція, яка обробляє всі три закінчення рядків ("\ 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/… та провів кілька тестів. Зараз все працює як слід.
Йохан Роде

1
@Thomas Weller: Конструктор і деструктор для часового виконуються. Це такі речі, як синхронізація потоків, пропуск пробілів та оновлення стану потоку.
Йохан Роде

1
У випадку EOF, яка мета перевірки tпорожнього місця перед встановленням eofbit. Чи не слід встановлювати цей біт незалежно від прочитаних інших символів?
Yay295

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

3
Це також читає порожній останній рядок, який не є поведінкою std::get_lineякого ігнорує порожній останній рядок. Я використовував такий код у випадку eof для імітації std::get_lineповедінки:is.setstate(std::ios::eofbit); if (t.empty()) is.setstate(std::ios::badbit); return is;
Patrick Roocks

11

Час виконання С ++ повинен правильно працювати з будь-якими умовами кінцевої лінії для вашої конкретної платформи. Зокрема, цей код повинен працювати на всіх платформах:

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

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

Звичайно, якщо ви маєте справу з файлами з іншої платформи, всі ставки вимкнені.

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

Наприклад, ви можете надати собі функцію стилю getline, яка виглядає приблизно так (не перевірено, використання індексів, підстрок тощо лише для педагогічних цілей):

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
@Neil, цієї відповіді ще недостатньо. Якби я хотів обробляти CRLF, я б не прийшов до StackOverflow. Справжня проблема полягає в тому, щоб обробляти файли, у яких є лише "\ r". У наш час вони досить рідкісні, тепер, коли MacOS наблизився до Unix, але я не хочу припускати, що вони ніколи не будуть подані на моє програмне забезпечення.
Аарон Макдейд

1
@Aaron ну, якщо ти хочеш мати змогу впоратися з ЩО-небудь, ти повинен написати свій власний код, щоб зробити це.

4
У своєму питанні я зрозумів із самого початку, що це легко обійти, маючи на увазі, що я хочу і можу це зробити. Я запитав про це, оскільки це, здається, таке поширене питання, і існує безліч форматів текстових файлів. Я припускав / сподівався, що комітет зі стандартів C ++ це вбудував. Це було моє запитання.
Аарон Макдейд,

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

8

Читаєте Ви файл у ДВОЙКОМ або в режимі ТЕКСТУ ? У режимі TEXT повернення пари / повернення рядка, CRLF , інтерпретується як TEXT кінець рядка або символ кінця рядка, але в BINARY ви отримуєте лише ОДИН байт за раз, що означає, що будь-який символ ПОВИНЕНбути проігнорованим і залишити в буфері для отримання як іншого байта! Повернення каретки означає на друкарській машинці, що машинка, на якій лежить друкарська рука, досягла правого краю паперу і повертається до лівого краю. Це дуже механічна модель, така як механічна друкарська машинка. Тоді подача рядка означає, що рулон паперу трохи повернутий вгору, щоб папір міг розпочати інший рядок набору тексту. Наскільки я пам’ятаю, одна з низьких цифр в 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);
...

буде найбільш правильним способом обробки всіх типів файлів. Однак зверніть увагу , що \ п в TEXT режим фактично байт пара 0x0d 0x0a, але 0x0d IS просто \ г: \ п включає \ г в TEXT режимі , але не в BINARY , так \ п і \ г \ п еквівалентні ... або має бути. Насправді це дуже основна галузева плутанина, типова галузева інерція, оскільки конвенція полягає в тому, щоб говорити про CRLF на ВСІХ платформах, а потім потрапляти в різні бінарні інтерпретації. Строго кажучи, файли , включаючи ТІЛЬКИ 0x0D (повернення каретки) як \ п (CRLF або рядки), які мають неправильний формат в TEXTрежим (машинка: просто поверніть машину і прочеркніть все ...), і є нелінійним орієнтованим двійковим форматом (або \ 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" - іноді ви дійсно хочете прочитати файл таким, яким він є (наприклад, для обчислення контрольної суми тощо), не змінюючи час виконання закінчень рядків.
Матіас

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 у старому стилі не повинні існувати ще довше, перевірте лише на \ \ та видаліть кінцевий символ \ \ r.


0

Якщо відомо, скільки елементів / чисел є в кожному рядку, можна прочитати один рядок, наприклад, 4 числа як

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

Це також працює з іншими закінченнями рядків.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.