Skip to content

Unicode in Perl

aero edited this page Sep 19, 2012 · 2 revisions

Unicode in Perl

 
Unicode란?

    세계의 각문자를 코드포인트(code point)에 배정시킨 것.
    코드(code)는 코드테이블의 코드값 자체를 말함
    인코딩(encoding)은 자료교환을 위해 코드를 특정한 형태로 표현하는 것 - [ USC-2, USC-4 ], UTF-8, UTF-16, UTF-32

    상세사항 참고
        http://en.wikipedia.org/wiki/Unicode
        http://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C
        http://www.kristalinfo.com/K-Lab/unicode/Unicode_intro-kr.html
        http://www.keris.or.kr/datafiles/data/KR2003-14.pdf
        http://joelonsoftware.com/articles/Unicode.html

 
유니코드의 기본구조

unicode.png
UCS코드 체계는 그림과 같이 그룹, 평면, 행, 셀 4개의 octet으로 구성되며 각 octet은 16진수 00~FF의 값을 가진다. UCS코드 체계의 부호화 공간은 128(0x00~0x7F)개의 그룹으로 구성되어 있으며 각 그룹은 256(0x00~0xFF)개의 평면을 가지고 각 평면은 256x256개의 cell을 가진다. 4개의 octet을 모두 사용하는 것을 UCS-4 라고 하며 평면하나만 사용해서 행 octet과 열 octet의 2개의 octets로 표현하는것을 UCS-2 라고 한다. 여기서 UCS-4는 4바이트 코드체계 이지만 최상위 bit는 늘 0으로 두고 나머지 31bit만을 사용하므로 그룹갯수는(0x00~0x7F)개가 된다. ( UCS-2, UCS-4 는 코드포인트이자 그 자체로 인코딩이다. )

 

    unicode 3.0 (ISO/IEC 10646-1:2000)
        16bit = 2 octets (ISO 용어) = 2 bytes (유니코드 컨소시엄 용어)
        0x0000 ~ 0xFFFD
        1개의 기본영역다중언어판(BMP, Basic Multilingual Plane) - 256x256 cells, |행octet|열octet| -> 코드포인트
        256*256 = 2^16 = 65,536개의 코드포인트, 실제사용 코드포인트는 49,194개, UCS-2(코드포인트이자 인코딩자체)

    unicode 3.1
        65,536개의 코드포인트로도 모자라자 보충언어판(supplementary planes - 0~16 총17개 모두 평면00에 속함, BMP는 0에 해당 )를 정의
        0x0000 ~ 0x10FFFD
        BMP의 2,048자를 대행코드(surrogates)로 할당하고 각 1,024자씩을 상위대행(high surrogates - 0xD800~0xDBFF),하위대행(low surrogates - 0xDC00~0xDFFF)로 정의하여 이 둘의 조합으로 다시 1024*1024=1048576 문자를 추가로 정의할 수 있도록 하였음 -> UTF-16
        ensurrogate:  $hi = ($uni - 0x10000) / 0x400 + 0xD800; , $lo = ($uni - 0x10000) % 0x400 + 0xDC00;
        desurrogate:  $uni = 0x10000 + ($hi - 0xD800) * 0x400 + ($lo -0xDC00);
        UTF-16 인코딩 -> 16bit에서는 UCS-2와 거의 동일, 보충언어판을 인코딩할때는 high/low surrogate의 조합(32bit)로 1048576개의 문자 추가 인코딩 가능

        0x10300 코드포인트를 UTF-16으로 인코딩 한다면 UCS-2영역을 벗어나므로 32bit로 인코딩해야함
            ensurrogate식에 $uni=0x10300을 대입하면 0xD800_DF00 으로 인코딩됨.(UTF-16에서는 상위대행코드가 나오면 반드시 하위대행코드가 따라옴)

현재는 Unicode 5.1 까지 나와있다.

 
Perl에서의 코드포인트 처리

    Perl에서는 특별히 surrogates(\x{D800}~\x{DFFF})에 대해서 사용을 금지하지 않으며(검사로 인한 속도저하 때문?) \x{0000_0000} ~ \x{ffff_ffff}영역은 모두 일단 문자로 취급한다.

 
Unicode의 인코딩

    UTF-8(문자에 따라 보통 1~4 또는 UCS-4 전 영역까지 포함할 경우 6바이트로 표현)
    UTF-16(2바이트혹은 보충언어판영역에 대해 high/low surrogate를 사용 4바이트로 표현) - UCS-2와 흡사하지만 Surrogate 영역을 이용하여 보충언어판을 표현할 수 있으므로 UCS-2는 UTF-16의 부분집합으로 볼 수 있음
    UTF-32(4바이트로 표현) - USC-4와 동일하지만 17개의 보충언어판만 지원하므로 UCS-4의 부분집합으로 볼 수 있음.
    Big Endian, Little Endian 머신에 따라 Byte order를 지정해주기 위해 인코딩 문자열 선두에 BOM을 붙임, UTF-8은 Byte order와 상관없는 인코딩이지만 Windows에서는 UTF-8에 대해서도 붙인다.(이것 때문에 Windows<->UNIX간 자료교환시 문제가 발생할 수 있으므로 주의)
    참고: http://en.wikipedia.org/wiki/Byte-order_mark

 
UTF-8이 널리 사용되는 이유

UTF-8를 제외한 인코딩 방식은 C등에서 전통적인 char형으로 문자열을 취급하게 되면 인코딩된 다중 byte의 일부분에 널(null,0)이 들어갈 수 있어 지금까지 개발된 프로그램들에서 문자열의 끝을 잘 못 인식하는 문제발생의 소지가 있으므로 ASCII 영역에서는 1바이트 UTF-8 인코딩과 완전히 일치되며 중간에 null바이트가 나타나지 않도록 고안되어 이런 문제가 발생하지 않는 UTF-8 인코딩이 널리 사용됨.
UTF-8 의 인코딩 규칙

             UCS-4                         UTF-8
0x00000000 - 0x0000007F    0xxxxxxx  (ASCII 영역에서는 일치 - ASCII코드만 사용하는 경우 기존의 프로그램 수정이 필요없다는 의미)
0x00000080 - 0x000007FF    110xxxxx 10xxxxxx
0x00000800 - 0x0000FFFF    1110xxxx 10xxxxxx 10xxxxxx
0x00010000 - 0x001FFFFF    11110xxx 10xxxxxx 10xxxxxx 10xxxxxx   ( 17개의 보충언어판을 포함한 범위는 0x10FFFD 까지이므로 4byte까지로 표현됨)
0x00200000 - 0x03FFFFFF    111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
0x04000000 - 0x7FFFFFFF    1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

 

utf-16 같이 surrogate pair에 의한 삽질(?)이 필요 없으며 코드포인트 자체를 규칙에 따라 그대로 인코딩 하면된다.

 
Perl의 유니코드

 

Perl에서 유니코드처리는 5.6.0 ~ 5.8.x 버젼을 거치면서 발달해왔다. 유니코드를 자체적으로 지원할 수 있는 기능추가는 5.6.0 버젼부터 시작되었다. 하지만 아주 엄밀하게 유니코드를 지원할 수 있다고 추천되는 버젼은 5.8.0 버젼부터 이며. 5.6.1 버젼에서 초기 유니코드 구현의 버그가 많이 수정되었지만 정규표현식 같은것은 여전히 5.6.1 버젼에서는 동작하지 않는다. 그 이전 버젼에서 유니코드 처리는 Perl의 자체적인 지원이 부족하여 Text::Iconv 같은 모듈 등등을 사용했지만 최근버젼들에서는 use utf8; pragma와 Encode모듈 등으로 Perl native한 지원이 거의 완벽해져서 따로 그 같은 모듈을 사용하지 않아도 된다.

 

문자열(Text string, Character string) - 사람이 읽을수 있는 문자들의 연속체

바이너리열(Binary string, Bytes string ) - Byte의 연속체, 문자열이 메모리에 저장된 형태, Perl process 외부와 소통하는 모든것들(인코딩은 어떤것이든 상관없음)

디코딩(decoding) - 특정 인코딩의 바이너리열을 펄 내부 유니코드포멧으로 변환하는 것

인코딩(encoding) - 펄 내부 유니코드 포멧을 특정인코딩의 바이너리열으로 변환하는 것

펄 내부 유니코드포멧(internal format) - 문자열을 내부포멧으로 인코딩(Perl은 utf-8을 사용)해서 메모리에 저장한다.

 
iso-8859-1(latin1) 인코딩

Perl은 기본으로는 문자열을 iso-8859-1(latin1)인코딩으로 저장한다.

( iso-8859-1(latin1)은 서유럽 언어의 표기에 필요한 ASCII의 영역을 넘어 ASCII에 없는 94개의 글자의 순차적 나열임 U+0000~U+00FF (을)를 0x00~0xff 의1 바이트에 매핑하는 인코딩 )

보통 euc-kr 같은 로컬인코딩의 경우 Byte 하나하나씩 봤을 때 iso-8859-1의 코드영역이 모두 커버하므로 그대로 입출력 가능하다. 하지만 euc-kr의 한글같은 경우 2바이트가 한글자이나 Perl은 이것을 한 문자라고 인식하지 못한다. 입출력시 그냥 건드리지 않고 처리.

 

codepoint( \x{16진수} )를 지정했을 경우 \x{ff}를 초과한 것들은 Perl이 유니코드로 인식하여 자동으로 내부포멧인 utf-8인코딩으로 변환되며 utf-8 flag가 켜진다. (이때 문자열 내에 ASCII영역(\x{00}~\x{7f}) 영역을 넘는 문자들은 모두 같이 인코딩이 utf-8으로 변경된다.) 문자열의 utf-8플래그가 켜지면 각종 문자열 처리함수 및 정규식등에서 바이트단위가 아닌 문자단위처리를 한다.

"eé가" 문자열에서 "é가"를 유니코드 codepoint로 지정하여 테스트

 

문자   codepoint   UTF-8

é       U+00E9        c3-a9

가      U+AC00       ea-b0-80

 

<코드>

#!/usr/bin/perl

use Devel::Peek;

my $s1="e\x{e9}\x{ac00}";
print length $s1,"\n";
Dump($s1);

 

<결과>

3            <- 내부적으로 UTF-8인코딩 6바이트이나 문자단위 처리를 하므로 문자열의 길이는 3으로 찍혔음
SV = PVMG(0x12c21f4) at 0x3d6f90
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,SMG,POK,pPOK,UTF8)         <- UTF-8 flag가 켜졌음
  IV = 0
  NV = 0
  PV = 0x12c34f4 "e\303\251\352\260\200"\0 [UTF8 "e\x{e9}\x{ac00}"]    <-  내부적 UTF-8 인코딩 ( e, \xc3,\xa9, \xea,\xb0,\x80 )
  CUR = 6
  LEN = 8
  MAGIC = 0x12c32fc
    MG_VIRTUAL = &PL_vtbl_utf8
    MG_TYPE = PERL_MAGIC_utf8(w)
    MG_LEN = 3

 
use utf8;

 

Perl에서 소스코드에 utf-8 인코딩 문자열을 사용하려면 use utf8; pragma를 사용한다. 선언후 wellformed utf8 인코딩 문자열이 나오면 Perl이 자동으로 Perl 내부 문자열 포멧으로 변경하면서 utf-8 flag를 넣어준다. 이 동작은 utf8 인코딩이 아니면서 0-127 범위를 벗어난 코드를 가지는 iso-8859-1(latin1)이나 기타 로컬코드문자가 들어오면 (utf-8인코딩 입장에서는 malformed utf-8으로 인식됨)억지로 utf8으로 변환하고자 하면서 깨지는 문제가 발생. 따라서 이 프래그마를 사용할 때는 소스인코딩을 꼭 utf8으로 하고 사용해야 한다. codepoint( \x{16진수} )를 지정할 경우 \x{ff}를 초과한 것들은 Perl이 유니코드로 인식하여 자동으로 내부포멧이 utf-8인코딩으로 변환되는건 마찬가지.

이 pragma는 소스코드가 utf-8인코딩을 사용하고 소스코드 안에서 눈으로 읽을 수 있는 utf-8문자열을 사용할 때 이외는 사용할 필요가 없다.

 

utf-8인코딩으로 코드를 작성하여 테스트

<코드>

#!/usr/bin/perl
use Devel::Peek;
use utf8;

my $s = "가";
print length $s,"\n";
Dump($s);

my $s1="\x{ac00}";
print length $s1,"\n";
Dump($s1);

my $s2="\x{ea}\x{b0}\x{80}"; 
print length $s2,"\n";
Dump($s2);

<결과>

1
SV = PVMG(0xa8191c) at 0x3d6044
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,SMG,POK,pPOK,UTF8)
  IV = 0
  NV = 0
  PV = 0x3dd9ec "\352\260\200"\0 [UTF8 "\x{ac00}"]
  CUR = 3
  LEN = 4
  MAGIC = 0xa823ac
    MG_VIRTUAL = &PL_vtbl_utf8
    MG_TYPE = PERL_MAGIC_utf8(w)
    MG_LEN = 1
1
SV = PVMG(0xa8193c) at 0x3d602c
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,SMG,POK,pPOK,UTF8)
  IV = 0
  NV = 0
  PV = 0x3df43c "\352\260\200"\0 [UTF8 "\x{ac00}"]
  CUR = 3
  LEN = 4
  MAGIC = 0xa82484
    MG_VIRTUAL = &PL_vtbl_utf8
    MG_TYPE = PERL_MAGIC_utf8(w)
    MG_LEN = 1
3           <--- utf-8 플래그가 켜져있지 않으므로 Byte기반으로 처리해서 3글자로 인식
SV = PV(0x3d617c) at 0xa9378c
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,POK,pPOK)
  PV = 0xa8398c "\352\260\200"\0                 <---  2번째와 바이너리열은 같지만 utf-8 flag가 켜져 있지 않다.(내부적 인코딩은 utf-8이지만 utf-8 flag가 켜져 있지 않으므로 Perl은 이것이 utf-8 인코딩이라고 인식하지 못한다.)
  CUR = 3
  LEN = 4

 
use encoding ...; pragma

http://search.cpan.org/perldoc?encoding

Dan Kogai씨가 만든 Encode 모듈에 포함되어 있는 기능이며 소스코드의 인코딩, 입출력/파일 I/O에 대한 인코딩을 지정하여 프로그램 내부로 읽어들이면 프로그램 내부적으로 모두 펄 내부 유니코드 포멧으로 변경하여 다루는 구조이나 use utf8; pragma와는 달리 ASCII영역(\x{00}~\x{7f})을 넘는 문자열 뿐만 아니라 바이너리열에 대해서도 무조건 utf-8인코딩으로 변환시켜 버리므로 기존 iso-8859-1을 사용하는 legacy코드들과 호환성에 문제가 있다. (만약에 바이너리 데이터를 다룬다면 \x80~\xff 영역의 데이터는 본의 아니게 펄 내부 유니코드 포멧 utf-8으로 인코딩하려고 하면서 다 깨져버린다.) Perl 유니코드 관련 문서들에 소개가 되고 있으나 최근 설계가 잘못되었다는 지적이 제기되며 Dan Kogai씨도 이를 인정하고 deprecate 시키고 싶다고 했으므로 사용하지 않을 것을 추천

<문제가 발생하는 예제>

#!/usr/bin/perl
use encoding 'euc-kr';  # 이것은 소스의 인코딩을 euc-kr로 함을 뜻한다.
use Devel::Peek;

my $s1 = "\xb0\xa1";  # euc-kr "가"의 인코딩된 바이트시퀀스 형태
my $s2 = "가";
my $s3 = "\xb0";  # 그냥 단순한 바이트 데이터 \xb0

Dump($s1);
Dump($s2);
Dump($s3);

 

<결과>

SV = PV(0x7a4dbc) at 0x7a4c78
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,POK,pPOK,UTF8)
  PV = 0x2403f8c "\352\260\200"\0 [UTF8 "\x{ac00}"]  <- 자동으로 utf8으로 변환되었다. 내부적으로 decode('euc-kr',"가") 과정이 일어난 것으로 보면 됨
  CUR = 3
  LEN = 4
SV = PV(0x7a4dd4) at 0x7a4c24
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,POK,pPOK,UTF8)
  PV = 0x244a9d4 "\352\260\200"\0 [UTF8 "\x{ac00}"]   <- 자동으로 utf8으로 변환되었다. 내부적으로 decode('euc-kr',"가") 과정이 일어난 것으로 보면 됨
  CUR = 3
  LEN = 4
SV = PV(0x7a4dec) at 0x7a4c54
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,POK,pPOK,UTF8)
  PV = 0x23fec24 ""\0 [UTF8 ""]  <- (문제발생)단순한 바이트 데이터일 뿐인 \xb0 이 예기치 않은 euc-kr -> utf8 인코딩 과정에 의해 깨져버렸다. 이런 건 건드리지 말아야 한다. 왜 use encoding 프래그마를 쓰지 않아야 하는지를 보여줌)
  CUR = 0
  LEN = 4

 

<다른 예>

use encoding 'utf8' STDOUT=>'euc-kr', STDIN=>'utf8';

위 프래그마는 소스인코딩은 utf8을 쓰며 만약 소스코드에서 utf8문자열이 나오면 자동으로 Perl 내부유니코드 포멧으로 변환한다.( 내부적으로 decode('utf8',"utf8문자열")의 과정이 일어남.) 만약에 표준출력으로 출력한다면(STDOUT=>'euc-kr')의 지정에 의해 자동으로 encode('euc-kr',"Perl내부유니코드포멧문자열")의 동작이 발생하며 표준 입력으로 부터 읽어들인다면 (STDIN=>'utf8')의 지정에 의해 decode('utf8',"입력utf8문자열")의 과정이 일어나기를 기대하며 선언하는 프래그마이다. 하지만 이것도 use encoding 프래그마가 가지는 문제(ASCII 영역을 넘는 바이트인 \x80~\xff 영역의 데이터를 펄 내부 유니코드 포멧 utf-8으로 인코딩하려고 하면서 다 깨져버리는 문제)를 동일하게 가지므로 use encoding 프래그마는 쓰지말자.

 
Perl에서 왜 유니코드처리시 문자가 깨지는가?

Perl은 내부적으로 wellformed utf8 인코딩이며 utf-8 flag가 켜져있을때 제대로된 유니코드 처리를 할 수 있다. Perl에서 문자열/데이터 처리시 깨지는 경우는 wellformed utf8/utf-8 flag on 데이터와 iso-8859-1인코딩 데이터, wellformed utf8이나 utf-8 flag가 켜져있지 않는 문자열이 합쳐질때 발생한다. 이경우 wellformed utf8/utf-8 flag on 데이터와 합쳐지는 데이터들은 byte하나하나가 codepoint로 인식되어 utf-8으로 인코딩 되므로 깨지게 되는것이다.

 

이런과정을 테스트해보면.

<코드>

#!/usr/bin/perl
use strict;
use warnings;
use Devel::Peek;

my $s1="\x{ea}\x{b0}\x{80}"; #wellformed utf-8/utf-8 flag off "가"
Dump($s1);
my $s2="\x{ac00}"; #wellformed utf-8/utf-8 flag on "가"
Dump($s2);
my $s3 = $s2.$s1;
Dump($s3);

<결과>

SV = PV(0x3d708c) at 0x3d6f48
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,POK,pPOK)
  PV = 0x3de8fc "\352\260\200"\0
  CUR = 3
  LEN = 4
SV = PV(0x3d70e0) at 0x3d6f30
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,POK,pPOK,UTF8)
  PV = 0x3dfb74 "\352\260\200"\0 [UTF8 "\x{ac00}"]
  CUR = 3
  LEN = 4
SV = PV(0x3d7110) at 0x3d6f6c
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,POK,pPOK,UTF8)
  PV = 0x12cf74c "\352\260\200\303\252\302\260\302\200"\0 [UTF8 "\x{ac00}\x{ea}\x{b0}\x{80}"]
  CUR = 9
  LEN = 12

 

처음과 두번째의 내부적 바이너리열은 같지만 첫번째는 utf-8 flag off 두번째는 on이다. 이 두개를 합쳤을때 첫번째 \x{ea}\x{b0}\x{80}가 각각 codepoint로 인식되어 \303\252\302\260\302\200 utf-8인코딩으로 잘 못 변환되었음을 볼 수 있다.

 
Perl에서 유니코드들을 깨지지 않게 하려면?

앞에서도 말했다시피 Perl에서 유니코드가 깨지지 않게 하려면 문자열을 조작하기전 모두 wellformed utf-8/ utf-8 flag on 상태(Perl 내부 유니코드 저장 포멧)로 만들 필요가 있으며  I/O(STDIN,file,socket등)를 통해 외부에서 받아들인 데이터는 명시적으로 wellformed utf-8/ utf-8 flag on 상태로 만들어야 한다.

(실제로는 Perl내부에서 어떠한 형태로 유니코드가 저장되는지는 사용자 입장에서 알 필요가 없으며 넣고 꺼낼때 명시적인 규칙을 따라서 해주면 내부 동작은 알 필요가 없는 블랙박스처럼 동작한다고 생각하면 된다.)

이런 상태로 만드는 방법에는 use utf8 프래그마를 쓰고 utf8인코딩으로 문자열을 사용하거나 읽어들이는 I/O Layer에 encoding을 명시적으로 지정하거나 일단 읽어들인 다음 Encode모듈의 decode 함수에 입력 인코딩을 명시적으로 지정하여 Perl 내부 문자열 포멧으로 변환 시키는 3가지 방법이 있다.

 

<euc-kr.txt> -> euc-kr 포멧

가나다

<코드>

#!/usr/bin/perl
use strict;
use warnings;
use Devel::Peek;
use Encode;

open my $fh1, '<', 'euc-kr.txt';
my $euc_kr = <$fh1>; # 일단 euc-kr 인코딩 자체로 읽어들임
print $euc_kr,"\n";
Dump($euc_kr);
my $utf_8 = decode('euc-kr',$euc_kr); # decode 함수로 Perl 내부 유니코드 저장 포멧으로 변경
print $utf_8,"\n"; # STDOUT(표준출력)의 인코딩이 utf-8으로 지정되어 있지 않으므로 Wide character in print 에러가 발생.
Dump($utf_8);

open my $fh2, '<:encoding(euc-kr)', 'euc-kr.txt'; # 파일 I/O에 대해 명시적으로 인코딩을 지정하여 읽어들임
# 이미 열린 파일핸들에 대해 인코딩을 지정하려면
# binmode $fh2, ':encoding(euc-kr)';
$utf_8 = <$fh2>;
binmode STDOUT, ':utf8';  # STDOUT의 인코딩을 명시적으로 utf-8인코딩으로 변경.
print $utf_8,"\n"; # Wide character in print 에러가 발생하지 않음
Dump($utf_8);

 

<결과>

가나다
SV = PV(0x3d708c) at 0x3d6f00
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,POK,pPOK)
  PV = 0x138d65c "\260\241\263\252\264\331"\0
  CUR = 6
  LEN = 80
Wide character in print at e.pl line 12, <$fh1> line 1.
媛€?섎떎
SV = PV(0x3d7530) at 0x1325e1c
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,POK,pPOK,UTF8)
  PV = 0x138a23c "\352\260\200\353\202\230\353\213\244"\0 [UTF8 "\x{ac00}\x{b098}\x{b2e4}"]
  CUR = 9
  LEN = 12
媛€?섎떎
SV = PVMG(0x1371b04) at 0x1325e1c
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,SMG,POK,pPOK,UTF8)
  IV = 0
  NV = 0
  PV = 0x138a23c "\352\260\200\353\202\230\353\213\244"\0 [UTF8 "\x{ac00}\x{b098}\x{b2e4}"]
  CUR = 9
  LEN = 12
  MAGIC = 0x13bca7c
    MG_VIRTUAL = &PL_vtbl_utf8
    MG_TYPE = PERL_MAGIC_utf8(w)
    MG_LEN = 3

 

결과를 보면 첫번째는 euc-kr 인코딩 그대로이며 두번째 세번째는 모두 Perl내부 유니코드 포멧으로 변환되었음을 볼 수 있다. 그리고 여기서 눈여겨 볼 것은 두번째의 Wide character in print 에러인데 이것은 Perl 내부 유니코드 포멧을 명시적으로 인코딩이 지정되지 않은 I/O 인터페이스로 출력하려고 할때 발생한다. 반면 3번째에서는 STDOUT에 대해 utf-8으로 인코딩을 명시적으로 지정했으므로 그런 에러가 발생하지 않았다. 만약 코드의 마지막에서 3째줄의 binmode STDOUT, ':utf8'; 를 binmode STDOUT, ':encoding(euc-kr)'; 로 바꾸고 실행하면 Perl내부 유니코드 포맷이 euc-kr 인코딩으로 잘 변환되어 원래의 euc-kr "가나다" 문자열이 출력될 것이다.

 
Perl Unicode 조언

Perl 유니코드에 관해서 perlunitut, perlunifaq 문서를 작성하는 등 깊은 지식을 가지고 있는 Juerd Waalboer씨의 Perl Unicode조언을 참고하면 많은 문제점을 사전에 회피할 수 있다.

참고: http://juerd.nl/site.plp/perluniadvice

 
기타참고 문서

 

    perluniintro - Perl Unicode introduction
    perlunicode - Unicode support in Perl
    Encode - character encodings
    Encode::Unicode - Various Unicode Transformation Formats

     Unicode-processing issues in Perl and how to cope with it
        http://ahinea.com/en/tech/perl-unicode-struggle.html

    Unicode support in Perl

     
        http://www.gg3.net/howto/perl-unicode.en
    http://catalyst.perl.org/calendar/2006/21
    http://www.excite.co.jp/world/korean/web/?wb_url=http://d.hatena.ne.jp/dayflower/20080219/1203493616
    http://j2k.naver.com/j2k_frame.php/korean/www.r-definition.com/program/perl/internalformat.htm
    http://www.excite.co.jp/world/korean/web/?wb_url=http://d.hatena.ne.jp/dayflower/20080313/1205380179
    http://j2k.naver.com/k2j_frame.php/korean/http://d.hatena.ne.jp/dayflower/20080620/1213925271
    http://j2k.naver.com/j2k_frame.php/korean/e8y.net/mag/015-encode/
    http://perlgeek.de/en/article/encodings-and-unicode

    http://j2k.naver.com/j2k_frame.php/korean/perl-users.jp/articles/advent-calendar/2009/casual/10.html

     

 
Perlmania( http://www.perlmania.or.kr ) 에 올라온 질문에 대한 답변 정리.

 

>과거에 use strict 를 사용하기 전에는, 한글 (UTF-8) 문자열을 폼으로 입력받아서
>화면상에 찍었을 때, 아무 문제 없이 화면상에 깨지지 않고 출력되었습니다만,
>새로 코드를 짜면서 테스트를 진행하니, 화면상에 출력되는 모든 UTF-8 한글 코드들은
>decode("utf8") 를 통해서 UTF-8 문자열임을 명시적으로 찍어주지 않으면 글자가 다 깨지는군요.
>덕분에 전에는 전혀 신경쓸 필요가 없던 출력단 부분에 일일이 decode 를 찍어주고 있습니다.
>(재미있는게, 정상적으로 출력되도록 decode를 찍어준 문자열이나, decode를 찍어주기 전 문자열이나 16진수 비교를 해보면 똑같습니다. -_-;;;)
>

일단 "decode를 찍어준 문자열이나, decode를 찍어주기 전 문자열이나 16진수 비교를 해보면 똑같습니다. " 에 대한 답은
두 문자열이 옥텟열(바이트 스트림)은 같은데 다르게 동작하는 것은 utf8 플래그가 있냐 없느냐의 차이일 겁니다. Perl은 내부적으로 UTF8인코딩으로 유니코드 문자열을 저장하는데 use utf8; 프래그마를 사용하고 utf8인코딩으로 코드를 작성하면 문자열에서 ASCII영역을 벗어난 문자가 있으면 utf8 플래그를 켭니다.
이것은 use Devel::Peek; 한다음 Dump(문자열)로 확인해보시면 utf8플래그를 볼 수 있을 겁니다.

Perl에서 unicode를 다룰 때 다음과 같은 기본 개념을 잡고 있으면 전혀 헷갈리지 않습니다.

                                     --인코딩지정-->
옥텟열(바이트스트림)           decode함수           Perl 내부유니코드 포멧
     (특정인코딩)              <--인코딩지정 --        (utf8인코딩,utf8플래그on)
                                       encode함수

Perl 내부유니코드 포멧은 사용자 입장에서는 사실 내부적으로 어떤 인코딩을 쓰고 어떤 방식으로 동작하는지 알 필요가 없습니다. 그냥 넣고 뺄 때 일관성있는 규칙과 방법을 적용하면 내부 동작을 신경 쓸 필요가 없기 때문에 일종의 블랙박스로 보면되죠..

1. Perl 프로세스 외부에서 파일,표준입력,소켓등의 I/O 통해 받아들이는 문자열은 단순히 옥텟열이다.

2. 옥텟열이 어떤 인코딩을 사용하고 있느냐를 알려줘서 그것을 Perl 내부유니코드포멧으로 변환하는 것이 Encode모듈의 decode 함수다.

( decode함수를 쓰지 않더라도 open my $fh, '<:utf8', 'file.txt'; 또는 binmode STDIN,':utf8'; 같이 I/O인터페이스에 대해 인코딩을 지정해주면 읽어들이면서 자동으로 Perl 내부 유니코드 포멧으로 바꿈 - 일종의 decode함수 같은 동작이 발생함 )
a. $str이 utf8 인코딩 옥텟열이면 decode('utf8',$str); ( 외부에서 읽어 들이는 것이 설령 utf8인코딩이라고 하더라도 그 인코딩이 어떤 것이다라는 것을 지정해주고 decode해야 Perl 내부 유니코드 포멧[utf8인코딩,utf8플래그 온] 으로 변환한다. 이 경우는 단순히 utf8플래그를 켜주는 역할)
* ':utf8'은 단순히 플래그만 on하며 ':encoding(UTF-8)' 인코딩의 정합성 까지 채크한다.
b. $str이 euc-kr 인코딩 옥텟열이면 decode('euc-kr',$str);
( 파일로부터 읽어들일 때는 open my $fh, '<:encoding(euc-kr), 'file.txt';
표준입력으로 부터는 binmode STDIN, ':encoding(euc-kr)'; 처럼 쓸 수 있음 )

3. a.의 경우 $str과 decode된 $str의 옥텟열은 같지만 다른 것은 decode 후의 문자열은 Perl 내부 유니코드포멧(utf-8)으로 변환된 상태이며 utf8플래그가 켜져 있는 상태이다. 이런 상태이면 Perl의 각종 문자열 처리함수들은 옥텟(바이트)단위가 아닌 문자 단위의 처리를 하므로 한 문자가 몇 바이트를 사용하는지 신경써서 경계를 수동으로 잘라주고 할 필요가 없다.

4. Encode 모듈의 encode 함수는 Perl내부 유니코드 포멧 문자열을 특정 인코딩의 옥텟열로 변환 하는 것이다.
a. $str이 Perl내부 유니코드 포멧 문자열이면 encode('euc-kr',$str); 하면 euc-kr 옥텟열이 나온다.

5. Perl 내부 유니코드 포멧의 문자열을 외부로 출력할 때는 해당 I/O 인터페이스에 대해 명시적으로 인코딩을 지정해야 한다. 그렇지 않으면 Wide character 어쩌고 저쩌고 하는 경고가 발생한다.
* $str이 Perl 내부 유니코드 문자열이면
binmode STDOUT, ':encoding(euc-kr)';
print $str;
하면 euc-kr로 출력되고
binmode STDOUT, ':encoding(UTF-8)';
print $str;
하면 utf-8으로 출력된다.
(일종의 encode 함수 같은 동작이 발생함)
You can’t perform that action at this time.