让std :: ifstream处理LF,CR和CRLF?

时间:2011-05-22 16:29:05

标签: c++ ifstream newline

具体来说,我对istream& getline ( istream& is, string& str );感兴趣。是否有ifstream构造函数的选项告诉它将所有换行编码转换为引擎盖下的'\ n'?我希望能够调用getline并让它优雅地处理所有行结尾。

更新:为了澄清,我希望能够编写几乎可以在任何地方编译的代码,并且几乎可以从任何地方获取输入。包括'\ r'没有'\ n'的稀有文件。最大限度地减少软件用户的不便。

解决这个问题很容易,但我仍然对标准中正确处理所有文本文件格式的方式感到好奇。

getline以字符串形式读取整行,最多为'\ n'。 '\ n'从流中消耗,但getline不包含在字符串中。到目前为止这很好,但是在'\ n'之前可能会有一个'\ r'被包含在字符串中。

在文本文件中看到three types of line endings: '\ 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.
}

修改感谢Neil指出f.good()不是我想要的。 !f.fail()是我想要的。

我可以自己手动删除它(请参阅此问题的编辑),这对于Windows文本文件很容易。但是我担心有人会输入一个只包含'\ r'的文件。在这种情况下,我认为getline将使用整个文件,认为它是一行!

..那甚至不考虑Unicode: - )

..也许Boost有一种很好的方法可以从任何文本文件类型一次消耗一行?

编辑我正在使用它来处理Windows文件,但我仍然觉得我不应该这样做!而且这不会为'\ r'唯一的文件分叉。

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

6 个答案:

答案 0 :(得分:103)

正如Neil指出的那样,“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 :(得分:10)

C ++运行时应正确处理特定平台的任何endline约定。具体来说,此代码应适用于所有平台:

#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;
}

答案 2 :(得分:7)

您是在 BINARY TEXT 模式下阅读文件吗?在 TEXT 模式下,对回车/换行 CRLF 被解释为 TEXT 行尾或行尾字符,但在 BINARY 一次只能获取 ONE 字节,这意味着要么忽略任何一个字符必须并将其保留在缓冲区中作为另一个字节获取!在打字机中,回车装置是打印臂所在的打字机车已经到达纸张的右边缘并返回到左边缘。这是一种非常机械的模型,是机械打字机的模型。然后换行意味着纸卷稍微向上旋转,因此纸张就位以开始另一行打字。正如我记得的那样,ASCII中的一个低位意味着移动到右边的一个字符而没有输入,死角,当然\ b表示退格:将汽车移回一个角色。这样你可以添加特殊效果,如底层(类型下划线),删除线(减去类型),近似不同的重音,取消(类型X),而不需要扩展键盘,只需通过调整汽车沿线的位置输入换行符。因此,您可以使用字节大小的ASCII电压来自动控制打字机,而不需要计算机。引入自动打字机时, AUTOMATIC 表示一旦到达纸张的最边缘,汽车将返回左侧 AND 所应用的换行,即当滚动向上移动时,假定汽车自动返回!因此,您不需要两个控制字符,只需要一个,\ 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 模式下的\ n实际上是字节对0x0d 0x0a,但是0x0d IS 只是\ r:\ n包含 TEXT 模式但不在 BINARY 中,因此\ n和\ r \ n是等效的...或应该是。这实际上是一个非常基本的行业混乱,典型的行业惯性,因为惯例是在所有平台上谈论CRLF,然后分为不同的二元解释。严格来说,包含 ONLY 0x0d(回车)的文件为\ n(CRLF或换行符),在 TEXT 模式下格式不正确(打字机:只需返回汽车和strikethrough everything ...),并且是一种非面向行的二进制格式(\ r或\ r \ n意味着面向行),所以你不应该读作文本!代码应该失败,可能有一些用户消息。这不仅取决于操作系统,还取决于C库的实现,增加了混淆和可能的变化......(特别是对于透明的UNICODE转换层添加了另一个令人困惑的变化的清晰点)。

以前的代码片段(机械打字机)的问题是,如果在\ r(自动打字机文本)之后没有\ n字符,则效率非常低。然后它还假定 BINARY 模式,其中C库被强制忽略文本解释(语言环境)并放弃纯粹的字节。两种模式之间的实际文本字符应该没有区别,仅在控制字符中,因此一般来说,读取 BINARY 优于 TEXT 模式。此解决方案对于 BINARY 模式典型的Windows操作系统文本文件非常有效,与C库变体无关,对其他平台文本格式(包括Web翻译到文本)效率低下。如果您关心效率,那么可以使用函数指针,按照您喜欢的方式对\ r vs \ r \ n \ n \ n \ n \ n \ n \ n对行控件进行测试,然后在指针中选择最佳的getline用户代码并从中调用它它。

顺便说一下,我记得我发现了一些\ r \ n \ n \ n \ n文本文件......这就像一些印刷文字消费者仍然需要的那样,翻译成双行文字。

答案 3 :(得分:1)

除了编写自己的自定义处理程序或使用外部库之外,你运气不好。最简单的方法是检查以确保line[line.length() - 1]不是'\ r'。在Linux上,这是多余的,因为大多数行最终会以'\ n'结尾,这意味着如果这是一个循环,你将失去相当多的时间。在Windows上,这也是多余的。然而,以'\ r'结尾的经典Mac文件呢? std :: getline不适用于Linux或Windows上的那些文件,因为'\ n'和'\ r''\ n'都以'\ n'结尾,因此无需检查'\ _ \'。显然,这些与这些文件一起使用的任务不会很好。当然,那里存在大量的EBCDIC系统,这是大多数图书馆都不敢解决的问题。

检查'\ r'可能是解决您问题的最佳方法。以二进制模式读取将允许您检查所有三个公共行结尾('\ r','\ r \ n'和'\ n')。如果您只关心Linux和Windows,因为旧式的Mac行结尾不应该存在更长时间,请仅检查'\ n'并删除尾随的'\ r'字符。

答案 4 :(得分:1)

一种解决方案是首先搜索并将所有行结尾替换为'\ n' - 就像例如Git默认使用。

答案 5 :(得分:0)

如果知道每行有多少个项目/编号,则可以读取一行,例如4个为

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

这也适用于其他行尾。