std :: ifstream을 사용하여 LF, CR 및 CRLF를 처리합니까?


85

특히 나는에 관심이 istream& getline ( istream& is, string& str );있습니다. 모든 개행 인코딩을 내부적으로 '\ n'으로 변환하도록 지시하는 ifstream 생성자에 대한 옵션이 있습니까? getline모든 라인 엔딩을 우아하게 처리하고 호출 할 수 있기를 원합니다 .

업데이트 : 명확히하기 위해 거의 모든 곳에서 컴파일되고 거의 모든 곳에서 입력을받는 코드를 작성할 수 있기를 원합니다. '\ n'없이 '\ r'이있는 희귀 파일을 포함합니다. 소프트웨어 사용자의 불편을 최소화합니다.

문제를 해결하는 것은 쉽지만 표준에서 모든 텍스트 파일 형식을 유연하게 처리하는 올바른 방법이 궁금합니다.

getline문자열로 최대 '\ n'까지 전체 줄을 읽습니다. '\ n'은 스트림에서 소비되지만 getline은 문자열에 포함하지 않습니다. 지금까지는 괜찮지 만 문자열에 포함되는 '\ n'바로 앞에 '\ r'이있을 수 있습니다.

텍스트 파일 에는 세 가지 유형의 줄 끝이 있습니다. '\ n'은 유닉스 머신의 일반적인 끝, '\ 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은 현재 OS에 표시되는 방식에 관계없이 새 줄을 의미합니다. 도서관에서 처리합니다. 그러나 그것이 작동하려면, 윈도우에서 컴파일 된 프로그램은 윈도우에서 텍스트 파일, 유닉스에서 컴파일 된 프로그램, 유닉스에서 텍스트 파일 등을
읽어야합니다

1
@George, Linux 컴퓨터에서 컴파일하고 있지만 때때로 Windows 컴퓨터에서 원래 가져온 텍스트 파일을 사용하고 있습니다. 내 소프트웨어 (네트워크 분석을위한 작은 도구)를 출시 할 수 있으며 사용자에게 거의 모든 시간에 (ASCII와 유사한) 텍스트 파일을 피드 할 수 있음을 알리고 싶습니다.
Aaron McDaid


1
if (f.good ())는 당신이 생각하는 것처럼 보이는 것을하지 않습니다.

1
@JonathanMee : 이럴 수도 있습니다 . 아마도.
궤도의 경쾌함 경주

답변:


111

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
@Miek : Bo Persons 제안 stackoverflow.com/questions/9188126/…에 따라 코드를 업데이트하고 몇 가지 테스트를 실행했습니다. 이제 모든 것이 정상적으로 작동합니다.
요한 Råde

1
@Thomas Weller : 센트리의 생성자와 소멸자가 실행됩니다. 스레드 동기화, 공백 건너 뛰기 및 스트림 상태 업데이트와 같은 작업을 수행합니다.
Johan Råde

1
EOF의 경우 teofbit를 설정하기 전에 비어 있는지 확인하는 목적은 무엇입니까 ? 다른 문자를 읽었는지에 관계없이 해당 비트를 설정해야하지 않습니까?
Yay295 2015 년

1
Yay295 : eof 플래그는 마지막 줄의 끝에 도달 할 때가 아니라 마지막 줄을 넘어서 읽으려고 할 때 설정되어야합니다. 이 검사는 마지막 줄에 EOL이 없을 때 이것이 발생하는지 확인합니다. (수표를 제거하고 마지막 줄에 EOL이없는 텍스트 파일에서 테스트 프로그램을 실행하면 볼 수 있습니다.)
Johan Råde

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

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 스타일 함수를 제공 할 수 있습니다 (테스트되지 않음, 교육 목적으로 만 인덱스, 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
@Neil,이 대답은 아직 충분하지 않습니다. CRLF를 처리하고 싶었다면 StackOverflow에 오지 않았을 것입니다. 진짜 문제는 '\ r' 있는 파일을 처리하는 것 입니다. MacOS가 유닉스에 가까워 졌기 때문에 요즘에는 매우 드물지만 내 소프트웨어에 절대로 공급되지 않을 것이라고 가정하고 싶지는 않습니다.
Aaron McDaid

1
@Aaron 잘, 당신이 무엇이든 다룰 수 있기를 원한다면 당신은 그것을하기 위해 당신 자신의 코드를 작성해야합니다.

4
저는 처음부터이 문제를 쉽게 해결할 수 있다는 점을 분명히했습니다. 이는 제가 그렇게 할 의지와 능력이 있음을 의미합니다. 일반적인 질문 인 것 같고 다양한 텍스트 파일 형식이 있기 때문에 이에 대해 물었습니다. 나는 C ++ 표준위원회가 이것을 구축했다고 가정 / 바랬다. 이것이 내 질문이었다.
Aaron McDaid

1
@Neil, 내가 잊은 또 다른 문제가 있다고 생각합니다. 하지만 먼저 지원할 적은 수의 형식을 식별하는 것이 실용적이라는 점을 인정합니다. 따라서 Windows와 Linux에서 컴파일되고 두 형식 모두에서 작동하는 코드를 원합니다. 귀하 safegetline는 솔루션의 중요한 부분입니다. 그러나이 프로그램이 Windows에서 컴파일되는 경우 바이너리 형식으로 파일을 열어야합니까? Windows 컴파일러 (텍스트 모드)에서 '\ n'이 '\ r' '\ n'처럼 작동하도록 허용합니까? ifstream f("f.txt", ios_base :: binary | ios_base::in );
Aaron McDaid

8

BINARY 또는 TEXT 모드 에서 파일을 읽고 있습니까? 에서 텍스트 모드 쌍 캐리지 리턴 / 라인 피드, CRLF는 ,로 해석됩니다 텍스트 라인의 끝, 또는 라인 문자의 끝,하지만에 BINARY 만 가져 ONE , 한 번에하는 수단을 바이트 그 중 문자 MUST무시되고 버퍼에 남겨져 다른 바이트로 가져옵니다! 캐리지 리턴은 타자기에서 인쇄 암이있는 타자기 자동차가 용지의 오른쪽 가장자리에 도달하여 왼쪽 가장자리로 되돌아가는 것을 의미합니다. 이것은 기계식 타자기의 매우 기계적인 모델입니다. 그런 다음 줄 바꿈은 용지 롤이 약간 위로 회전되어 용지가 다른 줄 입력을 시작할 수있는 위치에 있음을 의미합니다. 내가 기억하는대로 fas는 ASCII의 낮은 숫자 중 하나가 입력하지 않고 오른쪽 한 문자로 이동하는 것을 의미하고, 죽은 문자는 물론 \ b는 백 스페이스를 의미합니다. 자동차를 한 문자 뒤로 이동합니다. 이렇게하면 확장 키보드 없이도 기본 (밑줄 입력), 취소 선 (마이너스 입력), 다른 악센트에 가깝게, 취소 (X 입력)와 같은 특수 효과를 추가 할 수 있습니다. 라인 피드를 입력하기 전에 라인을 따라 차량의 위치를 ​​조정하기 만하면됩니다. 따라서 바이트 크기의 ASCII 전압을 사용하여 컴퓨터없이 자동으로 타자기를 제어 할 수 있습니다. 자동 타자기가 도입되면자동 수단이 용지의 먼 가장자리에 도달하면, 자동차가 왼쪽으로 반환됩니다 줄 바꿈이 적용, 즉, 자동차 롤 이동까지 자동으로 반환 할 가정한다! 따라서 두 제어 문자가 모두 필요하지 않고 하나만 필요합니다. \ 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이지만 0x0d 단지 \ r : \ n TEXT 모드에서는 \ r을 포함 하지만 BINARY 에서는 포함 하지 않으므로 \ n 및 \ r \ n은 동일합니다 ... 또는 해야한다. 이것은 매우 기본적인 산업 혼란입니다. 관례는 모든 플랫폼에서 CRLF에 대해 말하고 다른 이진 해석에 빠지는 것이기 때문에 일반적인 산업 관성입니다. 엄밀히 말하면 0x0d (캐리지 리턴) \ n (CRLF 또는 줄 바꿈)으로 포함 된 파일이 TEXT 에서 형식이 잘못되었습니다.mode (typewritter machine : just return the car and strikethrough everything ...), 비 라인 지향 바이너리 형식 (\ r 또는 \ r \ n은 라인 지향을 의미 함)이므로 텍스트로 읽어서는 안됩니다! 일부 사용자 메시지와 함께 코드가 실패해야합니다. 이것은 OS에만 의존하지 않고 C 라이브러리 구현에도 의존하여 혼란과 가능한 변형을 추가합니다 ... (특히 투명한 유니 코드 변환 레이어의 경우 혼란스러운 변형에 대한 또 다른 연결 지점을 추가합니다).

이전 코드 조각 (기계식 타자기)의 문제점은 \ r (자동 타자기 텍스트) 뒤에 \ n 문자가 없으면 매우 비효율적이라는 것입니다. 그런 다음 C 라이브러리가 텍스트 해석 (로케일)을 무시하고 순수한 바이트를 제공해야하는 BINARY 모드를 가정 합니다. 두 모드 간의 실제 텍스트 문자에는 차이가 없어야하며 제어 문자에서만 가능하므로 일반적으로 BINARY를 읽는 것이 TEXT 모드 보다 낫습니다 . 이 솔루션은 BINARY에 효율적입니다.모드 일반 Windows OS 텍스트 파일은 C 라이브러리 변형과 독립적이며 다른 플랫폼 텍스트 형식 (텍스트로의 웹 번역 포함)에는 비효율적입니다. 효율성에 관심이 있다면 함수 포인터를 사용하여 \ r 대 \ r \ n 라인 컨트롤을 원하는 방식으로 테스트 한 다음 가장 좋은 getline 사용자 코드를 포인터에 선택하고 다음에서 호출하십시오. 그것.

우연히도 일부 \ r \ r \ n 텍스트 파일도 발견 한 것을 기억합니다. 일부 인쇄 된 텍스트 소비자가 여전히 요구하는대로 이중 줄 텍스트로 변환됩니다.


"ios :: binary"의 경우 +1-때때로 런타임이 줄 끝을 변경하지 않고 파일을있는 그대로 (예 : 체크섬 계산 등) 읽고 싶을 때가 있습니다.
Matthias

2

한 가지 해결책은 먼저 Git이 기본적으로하는 것처럼 모든 줄 끝을 '\ n'으로 검색하고 바꾸는 것입니다.


1

사용자 정의 핸들러를 작성하거나 외부 라이브러리를 사용하는 것 외에는 운이 없습니다. 가장 쉬운 방법 line[line.length() - 1]은 '\ r'이 아닌지 확인하는 것 입니다. Linux에서는 대부분의 줄이 '\ n'으로 끝나기 때문에 이것은 불필요합니다. 즉, 이것이 루프에 있으면 상당한 시간을 잃게됩니다. Windows에서 이것은 또한 불필요합니다. 그러나 '\ r'로 끝나는 클래식 Mac 파일은 어떻습니까? std :: getline은 '\ n'및 '\ r' '\ n'이 모두 '\ n'으로 끝나고 '\ r'을 확인할 필요가 없기 때문에 Linux 또는 Windows에서 해당 파일에 대해 작동하지 않습니다. 분명히 해당 파일에서 작동하는 이러한 작업은 제대로 작동하지 않습니다. 물론 많은 EBCDIC 시스템이 존재하는데, 대부분의 도서관에서는 감히 다루지 못할 것입니다.

'\ r'을 확인하는 것이 문제에 대한 최선의 해결책 일 것입니다. 바이너리 모드로 읽으면 세 가지 공통 줄 끝 ( '\ r', '\ r \ n'및 '\ n')을 모두 확인할 수 있습니다. 예전 스타일의 Mac 라인 엔딩이 더 이상 존재하지 않아야하므로 Linux와 Windows에만 관심이 있다면 '\ n'만 확인하고 후행 '\ r'문자를 제거하십시오.


0

각 줄에 몇 개의 항목 / 번호가 있는지 알고 있다면, 예를 들어 4 개의 숫자가있는 한 줄을 다음과 같이 읽을 수 있습니다.

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

이것은 다른 줄 끝에서도 작동합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.