std :: ifstreamを取得してLF、CR、およびCRLFを処理しますか?


85

特に興味がありistream& getline ( istream& is, string& str );ます。ifstreamコンストラクターに、すべての改行エンコーディングを内部で「\ n」に変換するように指示するオプションはありますか?呼び出してgetline、すべての行末を適切に処理できるようにしたいと思います。

更新:明確にするために、私はほとんどどこでもコンパイルでき、ほとんどどこからでも入力を受け取るコードを書きたいと思っています。'\ n'のない '\ r'を持つまれなファイルを含みます。ソフトウェアのユーザーの不便を最小限に抑えます。

この問題を回避するのは簡単ですが、すべてのテキストファイル形式を柔軟に処理するための、標準での正しい方法についてはまだ興味があります。

getline'\ n'までの全行を文字列に読み込みます。'\ n'はストリームから消費されますが、getlineはそれを文字列に含めません。これまでのところ問題ありませんが、文字列に含まれる「\ n」の直前に「\ r」がある可能性があります。

テキストファイルに見られる行末に3つのタイプがあります。「\ 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()私が望んでいたものではないことを指摘してくれたNeilに感謝します。!f.fail()私が欲しいものです。

自分で手動で削除できます(この質問の編集を参照)。これは、Windowsテキストファイルの場合は簡単です。しかし、誰かが「\ r」だけを含むファイルをフィードするのではないかと心配しています。その場合、getlineは1行だと思って、ファイル全体を消費すると思います。

..そしてそれはUnicodeさえ考慮していません:-)

..おそらく、Boostには、任意のテキストファイルタイプから一度に1行を消費する優れた方法がありますか?

編集私はこれを使用してWindowsファイルを処理していますが、それでもそうする必要はないと感じています。そして、これは「\ r」のみのファイルをフォークしません。

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

2
\ nは、現在のOSで表示される方法に関係なく改行を意味します。図書館がそれを処理します。しかし、仕事のことについては、Windowsでコンパイルされたプログラムは、UNIXなどからテキストウィンドウからファイル、UNIXでコンパイルされたプログラム、テキストファイルをお読みください
ジョージKastrinis

1
@ George、Linuxマシンでコンパイルしているのに、Windowsマシンからのテキストファイルを使用していることがあります。私は自分のソフトウェア(ネットワーク分析用の小さなツール)をリリースするかもしれません、そして私はユーザーに(ASCIIのような)テキストファイルのほとんどいつでもフィードできることをユーザーに伝えたいです。
アーロンマクデイド


1
if(f.good())は、あなたが思っているようには機能しないことに注意してください。

1
@JonathanMee:それは次のようにされている可能性があり、この。多分。
軌道上でのライトネスレース

回答:


111

Neilが指摘したように、「C ++ランタイムは、特定のプラットフォームの行末規則が何であれ、正しく処理する必要があります。」

ただし、人々は異なるプラットフォーム間でテキストファイルを移動するため、それだけでは十分ではありません。3つの行末( "\ 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:BoPersonsの提案stackoverflow.com/questions/9188126/…に従ってコードを更新し、いくつかのテストを実行しました。これで、すべてが正常に機能します。
ヨハン・RADE

1
@Thomas Weller:歩哨のコンストラクタとデストラクタが実行されます。これらは、スレッドの同期、空白のスキップ、ストリーム状態の更新などを行います。
ヨハン・RADE

1
EOFの場合、teofbitを設定する前に空であることを確認する目的は何ですか。他の文字が読み込まれたかどうかに関係なく、そのビットを設定する必要はありませんか?
Yay295 2015年

1
Yay295:eofフラグは、最後の行の終わりに到達したときではなく、最後の行を超えて読み取ろうとしたときに設定する必要があります。このチェックにより、最後の行にEOLがない場合にこれが発生することが確認されます。(チェックを外してみて、最後の行にEOLがないテキストファイルでテストプログラムを実行すると、表示されます。)
JohanRåde2015年

3
これはまた、空の最後の行を読み取ります。これは、空の最後の行を無視する動作ではありませんstd::get_line。私はエミュレートするために、EOFの場合には、次のコードを使用std::get_line:行動is.setstate(std::ios::eofbit); if (t.empty()) is.setstate(std::ios::badbit); return is;
パトリックRoocks

11

C ++ランタイムは、特定のプラットフォームのエンドライン規則が何であれ、正しく処理する必要があります。具体的には、このコードはすべてのプラットフォームで機能するはずです。

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

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

もちろん、別のプラットフォームのファイルを扱っている場合は、すべての賭けが無効になります。

最も一般的な2つのプラットフォーム(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

ファイルをBINARYモードまたはTEXTモードで読み取っていますか?ではテキストモードペアキャリッジリターン/ラインフィード、CRLFは、と解釈されるテキスト行の終わり、または行末文字が、中にBINARYあなただけのフェッチONEた手段のいずれかの文字のことを、一度にバイトをMUST無視され、バッファに残されて別のバイトとしてフェッチされます!キャリッジリターンとは、タイプライターでは、印刷アームが置かれているタイプライターの車が用紙の右端に到達し、左端に戻ることを意味します。これは非常に機械的なモデルであり、機械式タイプライターのモデルです。次に、改行とは、ロール紙が少し上に回転して、用紙が別の入力行を開始できる位置にあることを意味します。私が覚えているように、ASCIIの下位桁の1つは、入力せずに右に1文字移動することを意味し、死んだ文字、そしてもちろん\ bはバックスペースを意味します。車を1文字戻します。そうすれば、拡張キーボードを必要とせずに、アンダースコア(アンダースコアのタイプ)、取り消し線(マイナスのタイプ)、さまざまなアクセントの近似、キャンセル(Xのタイプ)などの特殊効果を追加できます。ラインフィードに入る前に、ラインに沿って車の位置を調整するだけです。したがって、バイトサイズのASCII電圧を使用して、間にコンピューターがなくてもタイプライターを自動的に制御できます。自動タイプライターが導入されると、自動あなたが紙の最遠端に達すると、車が左に返されることを意味改行が適用され、それがあるが、車はロールが上方に移動すると自動的に返却されると仮定されます!したがって、両方の制御文字は必要ありません。1つだけ、\ n、改行、または改行が必要です。

これはプログラミングとは何の関係もありませんが、ASCIIは古くてHEYです!彼らがテキストのことを始めたとき、何人かの人々が考えていなかったように見えます!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という\ TEXTモードは、実際にバイトのペアに0x0dの0x0Aのですが、0x0DのはISだけ\ R:\ nは\ rを含んでいるテキストモードが、ではないBINARY ...、そう\ n、および\ rを\ nは同等ですか、する必要があります。これは実際には非常に基本的な業界の混乱であり、一般的な業界の慣性です。これは、すべてのプラットフォームでCRLFについて説明するため、さまざまなバイナリ解釈に分類されるためです。厳密に言えば、含むファイルONLYいる\ nは(CRLFまたは改行)として(キャリッジリターン)0x0Dをは、不正な形式にされているTEXTモード(タイプライターマシン:車を返してすべてを取り消し線で消す...)であり、行指向ではないバイナリ形式(\ rまたは\ r \ nは行指向を意味します)であるため、テキストとして読むことは想定されていません。コードは、おそらく何らかのユーザーメッセージで失敗するはずです。これはOSだけでなく、Cライブラリの実装にも依存し、混乱と考えられるバリエーションを追加します...(特に、バリエーションを混乱させるための別の調音点を追加する透過的なUNICODE変換レイヤーの場合)。

前のコードスニペット(機械式タイプライター)の問題は、\ r(自動タイプライターテキスト)の後に\ n文字がない場合は非常に非効率的であるということです。次に、Cライブラリがテキストの解釈(ロケール)を無視し、純粋なバイトを提供するように強制されるBINARYモードも想定しています。両方のモード間で実際のテキスト文字に違いはなく、制御文字のみであるため、一般的に言えば、BINARYを読む方がTEXTモードよりも優れています。このソリューションはBINARYに効率的ですモードは、Cライブラリのバリエーションとは無関係に一般的なWindows OSテキストファイルであり、他のプラットフォームのテキスト形式(テキストへのWeb翻訳を含む)には非効率的です。効率が気になる場合は、関数ポインターを使用し、\ rと\ r \ nのラインコントロールを好きなようにテストしてから、ポインターに最適なgetlineユーザーコードを選択して、そこから呼び出す方法があります。それ。

ちなみに、私はいくつかの\ r \ r \ nテキストファイルも見つけたのを覚えています...これは、一部の印刷されたテキスト消費者が依然として必要としているように、2行のテキストに変換されます。


「ios :: binary」の+ 1-ランタイムが行末を変更せずに、実際にファイルをそのまま読み取りたい場合があります(たとえば、チェックサムの計算など)。
マティアス

2

1つの解決策は、最初にすべての行末を検索して「\ n」に置き換えることです。たとえば、Gitがデフォルトで行うのと同じです。


1

独自のカスタムハンドラーを作成するか、外部ライブラリを使用する以外は、運が悪いです。最も簡単な方法line[line.length() - 1]は、「\ r」でないことを確認することです。Linuxでは、ほとんどの行が「\ n」で終わるため、これは不要です。つまり、これがループ内にあると、かなりの時間が失われます。Windowsでは、これも不要です。しかし、「\ r」で終わる古典的なMacファイルはどうですか?std :: getlineは、LinuxまたはWindowsのこれらのファイルでは機能しません。これは、「\ n」と「\ r」「\ n」の両方が「\ n」で終わり、「\ r」をチェックする必要がないためです。明らかに、これらのファイルで機能するこのようなタスクはうまく機能しません。もちろん、EBCDICシステムは数多く存在しますが、ほとんどの図書館はあえて取り組むことはありません。

'\ r'をチェックすることは、おそらくあなたの問題に対する最良の解決策です。バイナリモードで読み取ると、3つの一般的な行末( '\ r'、 '\ r \ n'、および '\ n')すべてを確認できます。LinuxとWindowsだけを気にする場合は、古いスタイルのMacの行末が長く続くべきではないため、「\ n」のみをチェックして、末尾の「\ r」文字を削除します。


0

各行にいくつのアイテム/番号があるかがわかっている場合、たとえば4つの番号を含む1行を読み取ることができます

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

これは、他の行末でも機能します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.