2011-05-22 19 views
67

特に私はistream& getline (istream& is, string& str);に興味があります。 ifstreamコンストラクタに、改行コードをすべて\ nに変換するように指示するオプションがありますか?私はgetlineに電話して、すべての行末を正常に処理したいと思っています。LF、CR、およびCRLFを処理するためにstd :: ifstreamを取得しますか?

アップデート:わかりやすくするために、ほぼどこでもコンパイルできるコードを書くことができるようにしたいと考えています。 '\ r'に '\ n'を含まない珍しいファイルを含める。ソフトウェアのユーザーにとっての不便さを最小限に抑えます。

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

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

テキストファイルには があります。 '\ 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'だけを含むファイルをフィードすることに心配しています。その場合、私はgetl​​ineがファイル全体を1行であると考えて消費することを想定しています!

...それも:-)

ユニコードを考慮していないが...多分ブーストは、任意のテキストファイルの種類から1行ずつ消費する良い方法を持っていますか?

私はこれを使ってWindowsファイルを処理していますそして、これは '\ r'専用のファイルではフォークしません。

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

\ nは、現在のOSでどのように表示されていても、新しい行を意味します。図書館はそれを世話します。しかし、それが動作するためには、ウィンドウでコンパイルされたプログラムは、ウィンドウからテキストファイル、Unixでコンパイルされたプログラム、UNIXなどのテキストファイルを読み込む必要があります。 –

+1

@George、Linuxマシンでコンパイルしていますが、元々はWindowsマシンから来たテキストファイルです。私はソフトウェア(ネットワーク解析のための小さなツール)をリリースするかもしれません、そして、私は彼らが(ASCIIのような)テキストファイルのほぼいつでもフィードできることをユーザーに伝えたいと思います。 –

+3

[あなたの問題を示す小さなテストケース](http://ideone.com/FanD7)。 –

答えて

94

、 " 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; 
} 
+0

これは私が必要とするものです。ありがとう。私の理解を助けるためのいくつかの質問。 #1 EOL文字の/ rを持つテストファイルでこれを実行するとwhileループに&&(t!= ""))を追加しない限り無限ループになります。私は別の方法と#2をやっていなければならないことを知りたいのですが、メソッドがistreamを返すのはなぜですか?ありがとう。 – Miek

+0

@Miek: 'std :: getline'インターフェースと一貫するために' istream& 'を返します。 '\ r'行末で報告する振る舞いはおそらく私のコードのバグでしょう。私はそれを見てみましょう。 – user763305

+0

お返事ありがとうございます。この投稿は実際にここでピックアップされ、ここで話しました。 http://stackoverflow.com/questions/9188126/c-strange-behavior-with-stdistream-or-sentry-wrap-around/9189541#9189541この問題は具体的に対処されています。私は無限ループを解決するために彼らの提案を実装し、それは動作しているようだ。ケース '/ n'のコードがEOFケースからコードを分離する必要があると言及しているはずであることについて、私は示唆しています。それは既に別れているように見えました。ただ空です。 – Miek

8

C++ランタイムは、特定のプラットフォームのエンドライン規約が正しく処理される必要があります。具体的には、このコードは、すべてのプラットフォーム上で動作するはずです:もちろん

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

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

を、あなたは別のプラットフォームからファイルを扱っている場合は、すべてのベットはオフになっています。

2つの最も一般的なプラットフォーム(LinuxとWindows)は改行文字で改行文字を使用していますが、その前に改行文字を使用すると、line文字列の最後の文字を調べてif \rであり、アプリケーション固有の処理を行う前に削除してください。

たとえば、あなたが(だけ教育的な目的のためにテストされていない、インデックスの使用、SUBSTRなど)は、このようになります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; 
} 
+8

質問は別のプラットフォームからファイルを処理する方法です。 –

+3

@Neil、この回答はまだ十分ではありません。私がCRLFを扱いたいと思ったら、私はStackOverflowに来ていないでしょう。本当の挑戦は** **だけが '\ r'を持つファイルを扱うことです。 MacOSがUnixに近づくようになった今、彼らはかなりまれですが、私は自分のソフトウェアに決して与えられないと思っていません。 –

+1

@Aaronもしあなたが何かを処理できるようにしたいのであれば、それを行うための独自のコードを書く必要があります。 –

1

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

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

7

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

は、すべてのタイプのファイルを処理する最も正しい方法です。注意ただしTEXTモードと、\ nは、バイトペアに0x0d 0x0aとは、実際にですが、 IS 0x0Dがちょうど\ R:TEXTモードでは、\ nは含ま\ rのではなく、BINARYので、\ n、および\ rの中\ nは同等であるか、そうでなければなりません。これは、すべてのプラットフォームでCRLFを話すことがコンベンションであるように、実際には典型的な業界の慣性である、非常に基本的な業界混乱です。厳密に言えば、のみを含むファイル 0x0d(復帰改行)は、\ n(CRLFまたは改行)であり、 TEXTモード(タイプライターマシン:車を戻してすべてを取り消します...)で整形されています。 (行方向を意味する\ rまたは\ r \ nのいずれかの)非行指向のバイナリ形式であるため、テキストとして読み込まないようにする必要があります。コードは、おそらくいくつかのユーザーメッセージで失敗するはずです。これは、OSにのみ依存するのではなく、Cライブラリの実装にも依存し、混乱や可能性のあるバリエーションを追加します...(特に透明なUNICODE変換レイヤーでは、混乱のために別のポイントを追加します)。

前のコードスニペット(メカニカルタイプライター)の問題は、\ r(自動タイプライターテキスト)の後に\ n文字がないと非常に効率が悪いということです。さらに、BINARYモードでは、Cライブラリはテキスト解釈(ロケール)を無視して、完全なバイトを渡すことを強制されます。両方のモードの間の実際のテキスト文字には制御文字でのみ違いがないはずです。したがって、一般的にはとなります。BINARYTEXTモードより優れています。この解決策は、Cライブラリのバリエーションとは無関係に、典型的なWindows OSテキストファイルのモードでは効率的であり、他のプラットフォームのテキスト形式(テキストへのWeb翻訳を含む)では非効率的です。BINARY効率性を気にするなら、関数ポインタを使い、\ r vs \ r \ nラインコントロールのテストを好きなようにしてから、ポインターに最適なgetlineユーザーコードを選択し、それを呼び出しますそれ。

ちなみに私はいくつかの\ r \ r \ nテキストファイルも見つけたことを覚えています。これはいくつかの印刷されたテキストコンシューマが必要とするように二重線テキストに変換されます。

1

解決策の1つは、まずすべての行末を検索して '\ n'に置き換えることです。デフォルトでGitは行います。

関連する問題