MenuIcon

Owl-Networks Archive

LoginIcon

Win32 환경에서 Perl 로 UTF-8, 유니코드 텍스트 파일 다루기 소고(小考)

| 분류: Perl | 최초 작성: 2013-01-10 22:40:53 |

0. 프롤로그

어쩌면 대한민국의 실무 현장에서 유니코드(UTF-16 LE) 형식의 파일을 핸들링해야 할 일은 그다지 많지 않을지도 모르겠습니다. 대부분의 경우에는 ANSI 호환 자국어 인코딩(대한민국의 경우 EUC-KR ≒ CP949)을 사용할 것이고, 다국어 내지 유니코드 문자세트의 사용이 필요한 경우라면 UTF-8 을 사용하리라 생각됩니다. 그러나, 불특정 다수의 텍스트 파일을 처리하는 프로그램을 작성하는 입장에서는, (한글)윈도우에서 기본적으로 사용되는 인코딩인 윈도우 유니코드(UTF-16 LE) 인코딩을 영 무시하기도 껄끄러운 것이 사실입니다. (저만 그런가요?)

이 문서는, 윈도우 환경에서 유니코드로 작성된 텍스트 파일을 Perl 로 읽어들여 편집 및 저장하는 과정에서 발생하는 갖가지 문제들에 대한 경험적 해결방안을 다룹니다. 이론적 바탕이 부족한 상태에서 경험적 지식을 바탕으로 작성된 문서이기 때문에, 정석적인 입장에서 보기에는 다소 변칙적이거나 부족할 수 있음을 우선 양해 부탁드립니다. 구체적으로, 다음과 같은 전제가 따릅니다.

  • 이 문서의 모든 테스트 결과는 한글 윈도우 7, Strawberry Perl 5.10.1.5 버전이 설치된 환경에서, CPAN 을 통해 Encode 모듈을 최신 버전(2.47)으로 업그레이드한 후에 도출된 것입니다.
  • 최소한 소프트웨어 레벨에서 윈도우 환경 하의 CP949, UTF-8, UTF-16 LE 이 세 가지 인코딩이라도 확실하게 지원하자는 견지에서 시작된 글이므로, UTF-16 LE 이외의 다른 유니코드 인코딩은 고려하지 않았습니다.
  • 글의 눈높이는 기초적인 Perl 코드를 작성할 수 있는 정도의 수준에 맞춥니다.

1. 텍스트 파일 핸들링의 기본

다음 내용은 이 글을 이해하기 위한 기본적인 전제가 되는 내용입니다. Perl 에서의 문자열 인코딩에 대해서 최소한 아래의 내용은 알고 보셔야 이 글이 이해가 됩니다. ("거침없이 배우는 Perl" 을 참조하십시오.)

  1. Perl 은 내부적으로 텍스트 문자열을 처리할 때, 특정한 텍스트 인코딩을 사용합니다. 이를 내부 인코딩이라고 합니다. Perl 에서 텍스트 작업을 할 때에는, 원래 인코딩의 바이트스트림 문자열을 그대로 가지고 작업하는 것보다, 문자열을 Perl 의 내부 인코딩으로 변환한 후에 작업을 하는 것이 좋습니다. 이 작업을 디코드(decode) 라고 하며, Encode 모듈의 decode() 메서드를 사용합니다.
  2. 이렇게 디코드된 문자열을 사용하여 작업을 마친 후에는, 다시 화면에 출력하거나 파일로 저장하기 위해서 다시 원래의 인코딩 (또는 사용자가 원하는 임의의 인코딩) 으로 변환되어야 합니다. 이 과정을 인코드(encode) 라고 하며, Encode 모듈의 encode() 메서드가 사용됩니다.
  3. Perl 이 사용하는 내부 인코딩은 UTF-8 입니다. 따라서, UTF-8 인코딩으로 작성된 텍스트 파일을 읽어들이는 경우를 가정하면, 문자열만 놓고 봤을 때는 디코드 전이나 후나 인코딩 자체는 변화가 없습니다. 그러나, 내부 인코딩으로 변환된 UTF-8 문자열은, 그것이 내부 인코딩의 (정상적인: Well-formed) UTF-8 문자열이라는 점을 알려주는 꼬리표(UTF-8 플래그)를 달고 있게 되며, 이것은 일반적인 UTF-8 인코딩의 문자열과 Perl 내부 인코딩으로서의 UTF-8 인코딩 문자열의 중요한 차이점입니다.
  4. 그 결과, 내부 인코딩으로 변환된 UTF-8 문자열과 내부 인코딩으로 변환되지 않은 UTF-8 바이트스트림 문자열은 Perl 내부적으로는 서로 다르게 취급됩니다. 같은 UTF-8 인코딩이라 하여 이들 문자열끼리 아무 생각 없이 결합하다가는 문자열이 다 깨질(Mal-formed UTF-8) 수 있습니다. (깨지지 않더라도, 이런 문자열을 print 함수 등으로 출력하면 Wide character in ~ 경고 메시지를 보게 될 것입니다.) 이런 문제를 예방하기 위해서 모든 텍스트 문자열을 읽을 때에 디코드 절차를 거치라는 것입니다.
  5. 반대 관점에서, Perl 이 어쨌거나 내부 인코딩으로 UTF-8 을 사용하기 때문에, 내부 인코딩 상태의 텍스트 문자열을 encode 작업 없이 그대로 저장하면 UTF-8 인코딩의 텍스트 파일이 됩니다. 따라서 항상 UTF-8 인코딩의 텍스트 문자열을 취급하는 경우에는 굳이 저장 시 encode 작업을 하지 않아도 큰 문제는 발생하지 않을 수 있습니다. 그러나, Perl 의 공식 문서에는 이런 식으로 Perl 의 내부 인코딩을 (자기 편할 대로) 이용하지 않도록 주의를 주고 있습니다. 따라서 항상 읽을 때는 decode, 쓸 때는 encode 를 반드시 거치도록 합니다. (이렇게 명확하게 처리해 주는 것이 예기치 않은 인코딩 관련 버그를 예방하는 방법입니다.)

이상의 내용을 그림으로 나타내면 다음과 같습니다:

Perl 의 Decode 와 Encode 의 관계도
Perl 의 Decode 와 Encode 의 관계도

Perl 에서의 문자열(특히 한글) 인코딩에 대한 한, 한글로 된 문서 중에서 가장 자세한 내용을 담고 있는 문서는 다음 문서입니다. 꼭 일독을 권합니다: http://gypark.pe.kr/wiki/Perl/한글

2. 윈도우 유니코드(UTF-16 LE)

세상에 존재하는 모든 문자를 하나의 단일한 문자세트로 기록하기 위한 유니코드 프로젝트지만, 이 유니코드 역시 그 표현 방식과 필요한 저장용량에 따라서 여러 가지 종류의 인코딩을 탄생시켰습니다. 하나의 문자를 표현하는 데 가변 크기를 사용하는지 고정 크기를 사용하는지, 고정 크기를 사용한다고 하더라도 하나의 문자를 표현하는 데 몇 바이트를 사용하는지에 따라서 여러 가지 종류의 유니코드 인코딩이 존재합니다.

이 글에서 다루고자 하는 것은, 그 중에서 윈도우 환경에서 유니코드 표현에 사용되는 UTF-16 LE 인코딩에 대한 것입니다. 윈도우는 여러 가지 유니코드 표시 방식 중에서, 하나의 문자를 표기하는 데에 2바이트(16비트)를 사용하는 방식을 채택하고 있으며, 그래서 이 방식을 UTF-16 이라고 합니다. 뒤의 LE 는 Little Endian 의 약자로서, 2바이트 코드를 기록하는 순서를 낮은 자리수부터 쓸 것인가, 큰 자리수부터 쓸 것인가를 결정합니다.

예를 들자면, UTF-16 LE 인코딩으로 "가" 를 표기하면 00 AC 로 표기됩니다. 반대의 표기 방법인 UTF-16 BE(Big Endian)로 "가"를 표기하면 AC 00 으로 표기되겠지요. (참고로 BE 표기 방식이 정방향이고, LE 표기 방식이 역방향입니다.) 왜 굳이 뒤집어 쓰는가에 대해서는 이 글의 주제와 관계 없으므로 이야기하지 않겠습니다(핵심만 간단히 말한다면, 전통과 속도의 문제입니다). 이미 인터넷에 이에 대해 설명한 많은 글들이 존재하므로 다른 문서를 참고하십시오. 다만, 이 문제는 뒤에서 읽기/쓰기와 관련하여 한번 더 논의해야 할 필요성이 있으므로, UTF-16 인코딩의 표현 형식에 UTF-16 LE/BE, UTF-16 의 세 가지 형태가 있다는 사실은 알고 계십시오.

앞으로 그냥 "윈도우 유니코드"라고 칭하는 경우, 모두 이 UTF-16 LE 인코딩을 의미합니다.

3. 일반적인 텍스트 파일 읽기 및 내부 인코딩으로 변환하기

만약, 입/출력되는 텍스트 파일의 인코딩이 윈도우 유니코드로 단일화되어 있는 환경이라면, 파일을 읽어들일 당시에 필터 레이어를 이용하여 바로 내부 인코딩으로 저장할 수 있습니다. 예를 들어,

my $str;

open my $fHandle, "<:raw:encoding(UTF-16LE):crlf", "filename.txt";
while (<$fHandle>) {
    $str .= $_;
}
close $fHandle;

위와 같이 :encoding 값을 지정해 주면, 이 파일핸들을 통해 읽어들이는 바이트스트림은 UTF-16LE 인코딩으로 간주되며, 그 결과 $str 스칼라 변수에 텍스트가 저장되면서 자동으로 내부 인코딩으로 변환되고 내부 인코딩 플래그까지 붙게 됩니다.

(참고로, :raw 는 Perl 의 binmode 와 같은 의미로, 전체를 바이너리 모드로 읽으라는, 즉 원래의 줄바꿈 문자(=개행 문자, 이하 모두 줄바꿈 문자로 쓰겠습니다.)를 손상시키지 말라는 의미가 됩니다. :encoding 뒤의 :crlf 는 줄바꿈 문자가 CR+LF 임을 알려줌으로써 줄바꿈 문자를 내부 단일 줄바꿈 문자로 변경하도록 합니다. 줄바꿈 문자에 대한 이야기는 뒤에서 다시 할 기회가 있습니다.)

그러나, 일반적인 환경에서 읽게 되는 텍스트 파일은, 그 인코딩이 항상 특정한 인코딩일 수만은 없습니다. 한글 윈도우에서 맞닥뜨리는 일반적인 환경만을 가정해 보더라도, 이 텍스트 파일은 대한민국에서 한글 표기 표준으로 오랫동안 써오던 KS-C 5601 완성형 및 그와 호환되는 확장완성형 인코딩(CP949)일 수도 있고, 현재 웹에서의 표준 인코딩이 되어가고 있는 UTF-8 인코딩일 수도 있습니다. 또 윈도우가 사용하는 2바이트 유니코드(UTF-16 LE) 일 수도 있겠네요. 한글 윈도우 환경이라면, 최소한 이 세 가지 인코딩은 고려를 해야만 할 것 같습니다.

따라서, 이런 경우에는 open 단계에서 인코딩을 지정하여 자동으로 내부 인코딩으로 변환하도록 하지는 못 할 것 같습니다. 인코딩을 지정하는 레이어 없이 일단 텍스트 모드로 읽어들인 후, Encode::Guess 모듈을 사용하여 인코딩을 추측하여 내부 인코딩으로 변환하도록 해야 하겠네요.

my $str;

open my $fHandle, "<", "filename.txt";
while (<$fHandle>) {
    $str .= $_;
}
close $fHandle;

use Encode;
use Encode::Guess;

my $enc = guess_encoding( $str, qw/cp949 utf8 UTF-16LE/ );

if ( ref($enc) ) {
    $str = decode( $enc->name, $str );
}
else {
    print "오류: 이 텍스트의 인코딩을 판단할 수 없습니다!\r\n";
    exit;
}

guess_encoding 메서드를 사용하면, Perl 은 지정된 $str 스칼라 변수에 저장된 텍스트의 인코딩이 뒤에 명시된 세 개의 인코딩 중 어느 것인지를 판단하여 $enc 에 레퍼런스 형태로 돌려주게 됩니다. guess_encoding 에서 돌려준 인코딩 이름은 위 소스에서 보는 바와 같이 바로 decode 에서 사용이 가능합니다. ($enc->name)

이것이 끝이라면 얼마나 좋겠습니까마는, 사실 문제는 지금부터입니다. 완벽해 보이는 위 코드는, UTF-16 LE 인코딩 및 몇몇 특수한 UTF-8 인코딩의 파일을 읽는 데에 허점이 존재합니다. 이제부터 그 부분을 파헤쳐 볼 겁니다.

4. UTF-8+BOM 텍스트 문서의 경우

앞에서 UTF-16 뒤에 붙은 LE/BE 꼬리표에 대해서 간단히 말씀드렸을 겁니다. UTF-16/32 의 경우에는, 바이트 순서를 정방향으로 쓰느냐 역방향으로 쓰느냐에 따라서 최소한 두 가지의 표기 형식이 있을 수 있기 때문에, 이들 인코딩으로 작성된 텍스트 문서에는 반드시 이 문서가 Big Endian 인지 Little Endian 인지 여부가 표시되어야 합니다. 그렇지 않으면 전혀 엉뚱한 문자열 값을 보여줄 수밖에 없을 테니까요. 이렇게 바이트 기록 순서를 표시하는 기호를 가리켜 Byte Order Mark (BOM) 라고 부르며, UTF-16 인코딩으로 저장된 텍스트 파일의 최선두에는 이 기호가 항상 따라오게 됩니다. (없을 수도 있긴 한데, 무시해도 좋습니다.) UTF-16 의 경우, 2바이트 크기의 BOM 이 붙으며, 텍스트 에디터 등에서는 이 문자는 보이지 않습니다. 이 값이 FF FE 라면 Little Endian 이며, FE FF 라면 Big Endian 입니다.

그렇다면, 텍스트 파일을 읽어서 내부 인코딩으로 전환된 후에는, 이 BOM 문자는 필요가 없으므로 제거해 주어야 원래 문자열을 다루는 데 방해가 되지 않을 것 같습니다. 일단 쓸데없는 2바이트 문자가 붙어 있어서 좋을 것도 없거니와, 이 BOM 이 그대로 붙어 있는 상태에서 문자열을 핸들링하다보면 반드시 문제가 생기게 마련이거든요.

우리의 Perl 은, UTF-16/32 인코딩이 사용된 텍스트 문서를 내부 인코딩으로 decode 하는 과정에서 이 BOM 문자를 자동으로 제거해 줍니다. 따라서, 파일을 읽어서 내부 인코딩으로 디코드했다면, 이 문자는 이미 제거되어 있는 상태이므로, 이 문자를 생각할 필요 없이 그냥 문자열만 있다고 생각하고 작업을 하면 됩니다. 즉, UTF-16 으로 작성된 유니코드 텍스트 파일을 읽을 때는 BOM 을 고민하지 않아도 됩니다.

그런데, BOM 을 고려해야 하는 경우가 있습니다. BOM 이 필요가 없는 UTF-8 인코딩으로 작성된 텍스트 파일인데도 BOM 을 달고 있는 경우가 있습니다. 단지 전통적인 이유 때문에 통용되는 것인데, 문제는 Perl 의 decode 모듈이 UTF-8 인코딩의 문서 앞에 붙은 BOM 은 자동으로 제거해 주지 않는다는 것입니다. 따라서, 수동 decode 시에 인코딩이 UTF-8 이라면 BOM 검사를 따로 해 주어야 BOM 문자로 인한 잠재적인 문제를 예방할 수 있습니다. 위 코드의 인코딩을 검출하고 내부 인코딩으로 decode 하는 부분을 조금 고쳐 보겠습니다.

use Encode;
use Encode::Guess;

my $enc = guess_encoding( $str, qw/cp949 utf8 UTF-16LE/ );
my $bom_flag = 0;

if ( ref($enc) ) {
    $str = decode( $enc->name, $str );

    if ( $enc->name eq "utf8" ) {
        if ( $str =~ /\x{FEFF}/ ) {
            $str =~ s/\x{FEFF}//g;
            $bom_flag = 1;
        }
    }
}
else {
    print "오류: 이 텍스트의 인코딩을 판단할 수 없습니다!\r\n";
    exit;
}

이제 만약 UTF-8 인코딩을 사용한 텍스트 파일인 경우 BOM 문자가 매칭되는지를 확인하고, 만약 매칭 된다면 이를 모두 제거하면서 $bom_flag 스칼라 변수의 값이 1로 바뀔 것입니다. $bom_flag 스칼라 변수에 값을 굳이 저장하는 이유는, 나중에 다시 파일을 저장할 때 원본에 BOM 이 있었던 경우 다시 BOM 을 기록해주기 위해서입니다. UTF-8 에서는 전혀 필요가 없는 값인데, 희한하게도 BOM 이 없으면 자기 데이터인데도 못 읽는 이상한 프로그램이 있더라고요. 그래서 보존을 하도록 하는 것입니다.

5. 윈도우 환경에서 Encode::Guess 모듈의 UTF-16 이름 표시 문제

이번엔 UTF-16 관련한 문제입니다. 좀 희한한 문제인데, 윈도우 환경에서 사용되는 Encode::Guess 모듈이 인코딩의 이름을 돌려주면서, UTF-16 인코딩의 세부 종류를 정확히 돌려주지 않는 문제입니다. 즉, 원본 텍스트 파일이 UTF-16 BE 이건, UTF-16 LE 이건 관계 없이 무조건 UTF-16 이라고 인코딩 이름을 돌려준다(관련 코드 링크)는 점이지요. 따라서, guess_encoding 메서드를 사용해서는 텍스트의 원래 바이트 순서가 BE인지 LE인지 알 수가 없습니다.

사실 읽기에서는 이건 별 문제가 안 됩니다. 이름을 이렇게 돌려주더라도 decode 자체는 제대로 하거든요. 문제는 기록을 할 때 발생합니다. 기록시 넘겨주는 인코딩 이름 값을 UTF-16 이라고만 던져줄 경우, Perl 은 이를 BOM 이 없는 UTF-16 BE 형태로 기록을 하게 됩니다. 졸지에 UTF-16 LE 가 UTF-16 BE 로 기록될 판입니다.

정확하게 하자면, 파일의 인코딩을 확인한 결과 UTF-16 이라면, 그 파일의 첫 2바이트를 바이너리 모드로 다시 읽어서 그 값이 FE FF인지 FF FE 인지를 검사한 후에 UTF-16 뒤에 LE 또는 BE 를 붙여 줘야만 합니다. 그러나 이건 좀 번거로운 일이고, 이미 서두에서 UTF-16 LE 만 고려하자고 했으니만치, UTF-16 이라면 자동으로 넘겨준 인코딩 이름 뒤에 LE 를 붙여주는 것으로 마무리를 짓도록 합시다.

use Encode;
use Encode::Guess;

my $enc = guess_encoding( $str, qw/cp949 utf8 UTF-16LE/ );
my $bom_flag = 0;

if ( ref($enc) ) {
    $str = decode( $enc->name, $str );

    if ( $enc->name eq "utf8" ) {
        if ( $str =~ /\x{FEFF}/ ) {
            $str =~ s/\x{FEFF}//g;
            $bom_flag = 1;
        }
    }
    elsif ( $enc->name eq "UTF-16" ) {
        $enc->name .= "LE";
    }
    else { }
}
else {
    print "오류: 이 텍스트의 인코딩을 판단할 수 없습니다!\r\n";
    exit;
}

6. 윈도우 환경에서 UTF-16 의 줄바꿈 문자 문제 (쓰레기 \r 문제)

윈도우 환경에서 윈도우 유니코드 파일을 핸들링할 때 가장 골치를 썩게 될 문제는 바로 이 문제입니다. 앞의 문제들은 좀 귀찮은 문제일 수는 있어도 치명적이라고까지는 하기 어려운 문제인데, 이 문제는 저장을 잘못하면 아예 원본 텍스트 파일이 망가질 수도 있는 문제이기 때문입니다.

앞에서 :raw 와 :crlf 에 대해서 간단히 쓰고 넘어왔습니다. 기본적으로 윈도우에서 작성된 텍스트 파일에서 줄바꿈 문자는 CR+LF(\r\n, HEX로 13 10) 입니다. 2바이트의 줄바꿈 문자를 사용하죠. 반면 Perl 은, 내부 인코딩의 텍스트에 대해서 유닉스의 용례를 따라 LF(\n) 를 줄바꿈 문자로 씁니다.

문제는, CP949 등 ANSI 호환 인코딩이나, UTF-8 인코딩의 텍스트 파일을 기본 레이어를 사용하여 읽을 때는 Perl 이 알아서 이 CR+LF 를 LF 로 바꿔 주지만, 윈도우 유니코드 텍스트 파일을 기본 레이어를 사용하여 읽을 때에는 이 CR+LF 를 LF 로 바꿔주지 않는다는 것입니다. 그래서 무슨 일이 생기느냐... 문자열 처리 때는 이것이 문제가 안 될 수도 있지만, 나중에 저장할 때에는 심각한 문제를 일으킵니다.

즉, Perl 이 내부 인코딩을 encode 를 통해 바이트스트림으로 변환할 때에, 내부 인코딩의 줄바꿈 문자 \n 을 자동으로 다시 \r\n 으로 변경하게 되는데(그렇게 레이어를 줄 것입니다), 이 과정에서 기계적으로 모둔 \n 이 \r\n 으로 변경됩니다. 그 결과, 원본이 유니코드였던 경우에, 원래의 \r이 떨어지지 않고 남아있던 상황에서 \n이 \r\n 으로 바뀌고, 그 결과 \r\r\n 이라는 괴상망칙한 줄바꿈 문자가 탄생합니다. 정확히 말하자면, 아무 짝에도 쓸모 없는 쓰레기 \r 문자가 줄바꿈 문자 바로 앞에 끼어들어버린 셈이죠. 이걸 그대로 저장하면? (...)

참고로, 이 문제는 이 글 처음에 본 것처럼 :encoding 레이어를 지정해서 파일을 읽으면서 바로 decode 를 한 경우에는 발생하지 않습니다. 단, 이 경우에는 또 다른 문제가 발생하는데, 그것은 이렇게 하면 BOM 문자가 떨어지지 않고 그대로 붙어온다는 것입니다. 따라서 줄바꿈 문자를 수동으로 제거해 주어야 합니다. 어쨌거나 읽을 당시에 인코딩이 확정되지 않는 현재의 상황에서는 쓸 수 없는 방법이네요.

외국 사이트에서도 이 쓰레기 \r 은 문제시되고 있고, 이를 해결하기 위해서 여러 가지 방법들이 제안되었습니다. 가장 확실한 방법은, 파일을 두 번 읽는 방법이었습니다. 처음 읽을 때는 인코딩을 추측하기 위해 읽고, 두 번째로 읽을 때에는 앞에서 추측한 인코딩을 넘겨주어 사용자가 사용할 수 있도록 정식으로 디코드 레이어를 주어 읽는다는 것입니다. 읽어들여야 할 텍스트 파일이 그다지 크지 않은 경우에는 괜찮은 대안입니다만, 파일이 큰 경우에는 시간이 낭비될 것 같습니다.

제가 사용하는 방법은, 텍스트를 그대로 읽되, 만약 인코딩이 UTF-16 이라면 치환을 사용하여 매칭되는 모든 \r 을 날려버리는 것입니다.

elsif ( $enc->name eq "UTF-16" ) {
    $enc->name .= "LE";
    $str =~ s/\r//g;
}

좀 무식한 방법이고, 약간의 문제의 여지도 있는 방법입니다. 만약 읽어들인 파일의 줄바꿈 문자가 매킨토시 형식의 줄바꿈 문자(\r)였을 경우에는 자칫 줄바꿈 문자가 다 날아가는 사태도 벌어질 수 있습니다. 윈도우 환경을 전제로 하고 있으므로 이런 방식의 무식한 해결을 했습니다만, 만약 매킨토시 쪽에서 작성한 텍스트 파일을 사용해야 하는 경우에는 다른 방법을 써야 합니다.

파일을 두 번 읽는 방법 말고, 줄바꿈 문자 처리에도 안전한 다른 하나의 대안을 도출해 본다면, 아예 바이너리 모드로 텍스트 파일을 읽고 쓰는 방법을 생각해 볼 수 있습니다. :encoding 이나 :crlf 레이어를 별도로 사용하지 않고 :raw 레이어 또는 binmode 만을 사용하여 바이너리 모드로 텍스트를 읽고 쓰는 것이지요.

my $str;

open my $fHandle, "<:raw", "filename.txt";
while (<$fHandle>) {
    $str .= $_;
}
close $fHandle;

이 방법을 사용하면, 문자열을 읽어들인 후 decode 메서드를 명시적으로 사용하여 내부 UTF-8 인코딩으로 변환하더라도 줄바꿈 문자의 변형이 일어나지 않습니다. 원본 그대로 따라가게 되죠. 따라서 원본의 줄바꿈 문자가 무엇이었는지도 파악이 가능하고, 이를 이용하면 원본의 줄바꿈 문자에 맞추어 저장할 때의 줄바꿈 문자를 지정할 수도 있을 겁니다. 모든 것을 수동 처리해야 하는 불편함이 있겠지만, 줄바꿈 문자의 처리가 좀 더 유연해진다는 장점도 있습니다.

7. 처리가 끝난 문자열을 다시 저장하기

문자열 처리를 다 했으면, 이제 저장을 해야죠. 저장의 경우에는 이미 원본의 인코딩을 알고 있기 때문에, 인코딩 레이어를 주어 저장하면 됩니다.

open my $fHandle, ">:raw:encoding($enc->name):crlf", "filename.txt";
print $fHandle "\x{FEFF}" if ( $enc->name eq "utf8" && $bom_flag == 1 ); 
print $fHandle "\x{FEFF}" if ( $enc->name eq "UTF-16LE" ); 
print $fHandle $str;
close $fHandle;
undef $fHandle;

내부 인코딩의 파일을 I/O 레이어를 사용하여 자동으로 encode 해서 저장하는 방법으로, 이게 가장 간편하고 또 많이 쓰이는 방법입니다. 줄바꿈 문자도 자동으로 CR+LF 로 변경되니 더욱 좋네요. 만약 $str 을 먼저 encode 한 후 :raw 로 저장하는 방법을 쓰면, 줄바꿈 문자가 CR+LF로 바뀌지 않기 때문에 저장 후의 줄바꿈 문자가 1바이트 유닉스 방식으로 변경되는 불상사가 벌어집니다. (^^)

단, Perl 은 BOM 문자가 필요한 경우라도 BOM 문자를 자동으로 써 넣어 주지는 않습니다. 따라서 필요한 경우 저장시 BOM 문자를 먼저 기록을 해 주어야 합니다. 예를 들면, 위 예제의 2-3행에서, UTF-8+BOM 의 경우 또는 UTF-16 LE 의 경우에 BOM 문자를 먼저 기록하고 있습니다.

참고로, Perl 5.8.x 버전과의 호환성을 위해서는, >:raw:encoding($enc->name):crlf 을 >:raw:encoding($enc->name):crlf:utf8 과 같이 마지막에 :utf8 을 명시하여 써야 한다고 합니다. 5.10 및 그 이후 버전에서 마지막의 :utf8 을 생략 가능합니다.

8. 정리하기

이제까지 다룬 내용을 정리해 보겠습니다.

  1. 텍스트 파일을 읽을 때는 항상 decode, 쓸 때는 encode 를 시행한다. (텍스트를 바이트스트림 그대로 핸들링하지 않는다.)
  2. 인코딩 파악을 위해 텍스트 데이터를 읽은 후 Encode::Guess 모듈을 사용한다.
  3. UTF-8 인코딩의 decode 시에는 BOM 여부를 수동으로 확인해야 한다.
  4. UTF-16 인코딩의 decode 시에는 기록할 때 바이트 순서가 변경되는 것을 막기 위해 인코딩 이름 뒤에 미리 LE 를 덧붙여 주어야 한다.
  5. UTF-16 인코딩의 decode 시 쓰레기 \r 이 남지 않도록 적절히 처리한다.
  6. 기록시 BOM 이 필요한 경우 함께 기록해야 한다.

9. 에필로그

이 글이 처음 작성된 때는 거의 한 달쯤 전입니다. SMI Sync 프로그램을 개정하면서 유니코드 파일의 핸들링 시 쓰레기 \r 문제의 해결을 위해 머리 싸매고 고민한 결과 나온 글이죠. 바로 블로그에 올리려다가, 마침 2012 성탄절 달력 행사가 진행중이라, 원고 펑크 사태가 벌어지면 슬쩍 땜빵 원고로 밀어넣으려고 블로그 등록을 잠시 미뤘었습니다..만 제가 갑자기 이런저런 일에 휘말린 덕에, 원고 전달이 되지 못 한 채로 어느 새 1월 초가 되어버렸네요.(^^)

프롤로그에서도 잠시 운을 떼긴 했습니다만, 현업에서 Perl 을 사용하면서 과연 유니코드 파일을 핸들링할 일이 있을지 모르겠습니다. 대부분 자기가 만든 데이터라면 UTF-8 아니면 CP949 인코딩의 데이터를 다루게 되겠죠. 게다가 윈도우에서 Perl 을 사용하는 인구가 거의 없을 것이기도 해서, 이 문서가 얼마나 도움이 될지도 모르겠습니다. 그러나 혹시라도, 비슷한 문제로 저처럼 머리 싸매고 고민할 분이 계실지도 모르기에, 이렇게 문서화를 해서 공개해 둡니다.

☞ 태그: UTF-16LE, Unicode, UTF-16, Perl, BOM, 유니코드, UTF-8,

☞ 트랙백 접수 모듈이 설치되지 않았습니다.

☞ 덧글이 없고, 트랙백이 없습니다.

덧글을 남기시려면 여기를 클릭하십시오.
[483] < [460] [454] [453] [452] [450] ... [449] ... [446] [445] [443] [442] [441] > [19]

(C) 2000-2018, Owl-Networks. Powered by Perl. 이 페이지는 HTML 5 표준에 따라 작성되었습니다.