Perl Unicode 입문

Perl Unicode 입문 (perluniintro)

Unicode가 뭔지, 그리고 Perl에서 Unicode를 어떻게 다루는지에 대한 전반적인 개념을 잡아주는 문서예요. Unicode를 더 깊이 다루는 자료는 "더 자세한 자료" 절을 참고하세요.

출처: perldoc - perluniintro

Unicode란 (Unicode)

Unicode는 세계의 모든 문자 체계와 그 외 많은 기호를 코드화하려는 문자 집합 표준이에요.

Unicode와 ISO/IEC 10646은 서로 조정된 표준으로, 다른 거의 모든 현대 문자 집합 표준을 통합해요. 80개 이상의 문자 체계와 수백 개의 언어(상업적으로 중요한 모든 현대 언어 포함)를 다루죠. 가장 큰 중국어·일본어·한국어 사전의 모든 글자도 인코딩돼 있어요. 이 표준은 결국 250개 이상의 문자 체계와 수천 개 언어의 거의 모든 문자를 다루게 될 거예요. Unicode 1.0은 1991년 10월, 6.0은 2010년 10월에 공개됐어요.

Unicode 문자는 추상적인 실체예요. 특정 정수 너비에 묶이지 않고, 특히 C 언어의 char와도 무관해요. Unicode는 언어 중립적이고 표시 중립적이에요 — 텍스트의 언어를 인코딩하지 않고, 일반적으로 폰트나 그래픽 레이아웃 세부사항을 정의하지 않아요. Unicode는 문자와 그 문자로 만들어진 텍스트 위에서 동작해요.

Unicode는 LATIN CAPITAL LETTER AGREEK SMALL LETTER ALPHA 같은 문자와, 그 문자에 대한 고유 번호(각각 0x00410x03B1)를 정의해요. 이 고유 번호를 **코드 포인트(code point)**라고 불러요. 코드 포인트는 본질적으로 가능한 모든 Unicode 문자 집합 안에서 그 문자의 위치예요. 그래서 Perl에서는 **서수(ordinal)**라는 용어를 종종 그와 바꿔 쓸 수 있어요.

Unicode 표준은 코드 포인트에 16진수 표기를 선호해요. 0x0041 같은 숫자가 낯설다면 뒤에 나오는 "Hexadecimal Notation" 절을 잠깐 보세요. Unicode 표준은 U+0041 LATIN CAPITAL LETTER A처럼 표기해서 16진수 코드 포인트와 문자의 표준 이름을 함께 줘요.

Unicode는 또 문자들에 대해 "대문자", "소문자", "십진 숫자", "구두점" 같은 각종 **속성(property)**을 정의해요. 이 속성들은 문자 이름과 무관해요. 그리고 대문자화, 소문자화, 정렬(collating) 같은 문자에 대한 다양한 연산도 정의돼 있어요.

Unicode 논리 "문자" 하나가 실제로는 둘 이상의 내부 실제 "문자"/코드 포인트로 구성될 수 있어요. 서양 언어에서 이는 기본 문자(예: LATIN CAPITAL LETTER A) 뒤에 하나 이상의 수식 문자(예: COMBINING ACUTE ACCENT)가 오는 것으로 충분히 모델링돼요. 기본 문자와 수식 문자의 이 시퀀스를 **결합 문자 시퀀스(combining character sequence)**라고 불러요. 일부 비서양 언어는 더 복잡한 모델이 필요해서, Unicode가 grapheme cluster 개념을 만들었고 이는 나중에 **확장 grapheme cluster(extended grapheme cluster)**로 더 정제됐어요. 예를 들어 한글 음절은 하나의 논리 문자로 여겨지지만, 대개 세 개의 실제 Unicode 문자(초성 자음 + 중성 모음 + 종성 자음)로 구성돼요.

이 확장 grapheme cluster를 "문자"라고 부를지는 관점에 달려 있어요. 프로그래머라면 시퀀스의 각 요소를 하나의 단위, 즉 "문자"로 보는 경향이 있겠죠. 하지만 사용자 관점에서는 전체 시퀀스가 하나의 "문자"로 보일 수 있어요 — 그게 사용자 언어 문맥에서 보이는 모양이니까요. 이 문서에서는 프로그래머 관점을 취해요: 하나의 "문자"는 하나의 Unicode 코드 포인트예요.

기본 문자와 수식 문자의 일부 조합에는 미리 결합된(precomposed) 문자가 있어요. 예컨대 LATIN CAPITAL LETTER A 뒤에 COMBINING ACUTE ACCENT가 오는 시퀀스에 해당하는 단일 문자가 있는데, 이를 LATIN CAPITAL LETTER A WITH ACUTE라고 불러요. 하지만 이런 미리 결합된 문자는 일부 조합에서만 쓸 수 있고, 주로 Unicode와 레거시 표준(예: ISO 8859) 간의 왕복 변환을 지원하기 위한 거예요. Unicode처럼 시퀀스를 쓰면 더 적은 기본 빌딩 블록(코드 포인트)으로 훨씬 더 많은 grapheme cluster를 표현할 수 있어요. 동등한 형태 사이의 변환을 지원하기 위해 다양한 **정규화 형태(normalization form)**도 정의돼 있어요. 그래서 LATIN CAPITAL LETTER A WITH ACUTE는 정규화 형태 Composed(NFC)에 있고, LATIN CAPITAL LETTER A 뒤에 COMBINING ACUTE ACCENT가 오는 시퀀스는 같은 문자를 Decomposed(NFD) 형태로 나타낸 거예요.

레거시 인코딩과의 역호환성 때문에 "모든 문자에 고유 번호 하나"라는 생각은 조금 무너져요. 대신 "모든 문자에 번호가 적어도 하나는 있다"가 되는 거죠. 같은 문자가 여러 레거시 인코딩에서 다르게 표현될 수 있어요. 그 역은 성립하지 않아요 — 어떤 코드 포인트에는 배정된 문자가 없어요. 첫째, 사용되는 블록 안에도 할당되지 않은 코드 포인트가 있고, 둘째, 실제 문자를 나타내지 않는 특수 Unicode 제어 문자가 있어요.

Unicode가 처음 구상됐을 때는 세계의 모든 문자가 16비트 워드로 표현될 거라 생각했어요. 즉 0x0000부터 0xFFFF까지, 최대 0x10000(65,536)개로 충분하다고 봤죠. 이는 곧 틀렸음이 드러났고, Unicode 2.0(1996년 7월)부터 Unicode는 21비트(0x10FFFF)까지 정의됐어요. Unicode 3.1(2001년 3월)이 0xFFFF 위의 첫 문자들을 정의했죠. 처음 0x10000개의 문자를 Plane 0 또는 **기본 다국어 평면(BMP)**이라고 불러요. Unicode 3.1로 총 17개 평면이 정의됐지만, 아직 정의된 문자로 꽉 차 있는 것과는 거리가 멀어요.

새 언어를 인코딩할 때 Unicode는 대개 그 문자들에 연속된 미할당 코드 포인트 블록을 골라요. 지금까지 이런 블록의 코드 포인트 수는 항상 16으로 나누어떨어졌어요. 블록에서 현재 필요하지 않은 여분은 미래 성장을 위해 미할당으로 남겨요. 하지만 나중 릴리스에서 가용 여분보다 더 많은 코드 포인트가 필요해, 초기 블록과 인접하지 않은 곳에 새 블록을 할당해야 했던 경우도 있었어요. 그래서 일찍부터 "블록"이 적절한 조직 원리가 아니라는 게 드러났고, Script 속성이 만들어졌어요. (나중에 개선된 script 속성인 Script_Extensions도 추가됐어요.) 오버플로 블록의 코드 포인트들도 원래 것과 같은 script를 가질 수 있어요. script 개념은 자연 언어에 더 잘 맞아요 — 라틴 script, 그리스 script 같은 게 있고, 수학 기호처럼 여러 script에서 쓰이는 문자들을 위한 Common 같은 인공 script도 여럿 있어요. script는 대개 여러 블록의 다양한 부분에 걸쳐 있어요. script에 대한 자세한 내용은 perlunicode의 "Scripts"를 참고하세요. 블록으로의 분할은 존재하지만 거의 완전히 우연이에요 — 문자가 지금까지 그리고 지금도 어떻게 할당되는지의 산물이죠. (이 문단은 입문용이라 단순화했음을 유의하세요. Unicode는 언어를 인코딩하지 않고 그 언어의 문자 체계, 즉 script를 인코딩해요. 하나의 script는 많은 언어가 쓸 수 있어요. Unicode는 BAGGAGE CLAIM 같은 언어와 무관한 기호도 인코딩해요.)

Unicode 코드 포인트는 그저 추상적인 숫자예요. 이 추상 숫자를 입출력하려면 숫자를 어떻게든 인코딩하거나 직렬화해야 해요. Unicode는 여러 문자 인코딩 형태를 정의하는데, 그중 UTF-8이 가장 인기 있어요. UTF-8은 Unicode 문자를 1~4바이트로 인코딩하는 가변 길이 인코딩이에요. 다른 인코딩으로는 UTF-16과 UTF-32, 그리고 그 빅·리틀 엔디언 변형이 있어요(UTF-8은 바이트 순서와 무관). ISO/IEC 10646은 UCS-2와 UCS-4 인코딩 형태를 정의해요.

인코딩에 대한 더 자세한 내용 — 예를 들어 서러게이트(surrogate)와 바이트 순서 표시(BOM)가 뭔지 — 는 perlunicode를 참고하세요.

Perl의 Unicode 지원 (Perl's Unicode Support)

Perl v5.6.0부터 Perl은 Unicode를 네이티브로 처리할 수 있게 됐어요. 하지만 심각한 Unicode 작업에 권장되는 첫 릴리스는 Perl v5.8.0이에요. 유지보수 릴리스 5.6.1은 초기 Unicode 구현의 많은 문제를 고쳤지만, 예를 들어 5.6.1에서 정규식은 여전히 Unicode와 동작하지 않아요. Perl v5.14.0은 Unicode 지원이 (거의) 몇몇 함정 없이 원활하게 통합 가능한 첫 릴리스예요. (예외가 몇 가지 있어요. 첫째, quotemeta의 일부 차이는 Perl 5.16.0부터 고쳐졌어요. 둘째, 범위 연산자의 일부 차이는 5.26.0부터. 셋째, split의 일부 차이는 5.28.0부터 고쳐졌어요.)

이 원활한 지원을 켜려면 feature 'unicode_strings'를 사용해야 해요 (v5.12 이상을 쓰면 자동으로 선택돼요). feature 참고. (5.14는 또한 여러 버그와 Unicode 표준과의 이탈을 고쳐요.)

Perl v5.8.0 이전에는 use utf8이 '현재 블록이나 파일의 연산이 Unicode를 인식하겠다'고 선언하는 데 쓰였어요. 이 모델은 틀렸거나 적어도 서툴렀다는 게 드러났어요 — "Unicodeness"는 이제 연산에 붙는 게 아니라 데이터와 함께 운반되거든요. Perl v5.8.0부터 use utf8을 명시적으로 써야 하는 경우는 하나만 남았어요. Perl 스크립트 자체가 UTF-8로 인코딩돼 있다면, use utf8을 쓰는 것으로 식별자 이름, 문자열·정규식 리터럴에서 UTF-8을 쓸 수 있어요. 이는 기본값이 아니에요 — 레거시 8비트 데이터를 담은 스크립트가 깨지기 때문이에요. utf8 참고.

Perl의 Unicode 모델 (Perl's Unicode Model)

Perl은 5.6 이전의 8비트 네이티브 바이트 문자열과 Unicode 문자 문자열을 모두 지원해요. 일반 원칙은 Perl이 데이터를 가능한 한 오래 8비트 바이트로 유지하려 하지만, Unicodeness를 피할 수 없게 되면 데이터가 투명하게 Unicode로 업그레이드된다는 거예요. Perl v5.14.0 이전에는 업그레이드가 완전히 투명하지 않았고(perlunicode의 "The Unicode Bug" 참고), 역호환성 때문에 use feature 'unicode_strings'(feature 참고)나 use v5.12 이상을 선택하지 않으면 완전한 투명성을 얻지 못해요.

내부적으로 Perl은 현재 플랫폼의 네이티브 8비트 문자 집합(예: Latin-1), 기본값은 UTF-8을 써서 Unicode 문자열을 인코딩해요. 구체적으로, 문자열의 모든 코드 포인트가 0xFF 이하면 Perl은 네이티브 8비트 문자 집합을 쓰고, 그렇지 않으면 UTF-8을 써요.

Perl 사용자는 보통 Perl이 내부 문자열을 어떻게 인코딩하는지 알 필요도 없고 신경 쓸 필요도 없어요. 하지만 PerlIO 레이어가 없는("기본" 인코딩의) 스트림에 Unicode 문자열을 출력할 때는 관련이 있어요. 그런 경우 내부에서 쓰이는 원시 바이트(각 문자열에 적절한 네이티브 문자 집합 또는 UTF-8)가 출력되는데, 그 문자열에 0x00FF를 넘는 문자가 있으면 "Wide character" 경고가 나와요.

예를 들어

perl -e 'print "\x{DF}\n", "\x{0100}\x{DF}\n"'

는 상당히 쓸모없는 네이티브 바이트와 UTF-8의 혼합물을 만들어내고, 경고도 나와요:

Wide character in print at ...

UTF-8로 출력하려면 :encoding이나 :utf8 출력 레이어를 써요. 이 샘플 프로그램에

binmode(STDOUT, ":utf8");

을 앞에 붙이면 출력이 전부 UTF-8임을 보장하고 경고도 사라져요.

표준 파일핸들·기본 open() 레이어·@ARGV의 자동 UTF-8화는 -C 명령행 스위치나 PERL_UNICODE 환경 변수로 켤 수 있어요 (perlrun의 -C 문서 참고).

이는 Perl이 다른 소프트웨어도 같은 방식으로 동작할 것을 기대한다는 뜻이에요. Perl이 STDIN이 UTF-8이라고 믿게 됐는데, 다른 명령에서 들어온 STDIN이 UTF-8이 아니면 Perl은 잘못된 UTF-8에 대해 불평할 가능성이 커요.

Unicode와 I/O를 결합하는 모든 기능은 새로운 PerlIO 기능을 요구해요. 거의 모든 Perl 5.8 플랫폼이 PerlIO를 쓰는데, 자신의 것이 쓰는지 확인하려면 "perl -V"를 실행해 useperlio=define을 찾아보세요.

Unicode와 EBCDIC (Unicode and EBCDIC)

Perl 5.8.0이 EBCDIC 플랫폼에서의 Unicode 지원을 추가했어요. 이 지원은 이후 릴리스에서 잠시 중단됐다가 5.22에서 부활했어요. 추가 변환이 필요해서 Unicode 지원은 구현이 좀 더 복잡해요. 더 자세한 내용은 perlebcdic를 참고하세요.

EBCDIC 플랫폼에서 내부 Unicode 인코딩 형태는 UTF-8 대신 UTF-EBCDIC이에요. 차이는, UTF-8이 "ASCII 안전"해서 ASCII 문자들이 그대로 UTF-8로 인코딩되는 반면, UTF-EBCDIC은 "EBCDIC 안전"해서 모든 기본 문자("A", "0", "%"처럼 ASCII 등가물을 가진 모든 문자 포함)가 EBCDIC과 UTF-EBCDIC에서 같다는 점이에요. 문서에서 종종 "UTF-8"이라는 용어가 UTF-EBCDIC까지 뜻하곤 해요. 이 문서도 그렇습니다.

Unicode 만들기 (Creating Unicode)

이 절은 v5.22부터의 Perl에 완전히 적용돼요. 이전 릴리스에 대한 각종 주의사항은 아래 "Earlier releases caveats" 하위 절에 있어요.

리터럴에서 Unicode 문자를 만들려면 큰따옴표 문자열에서 \N{...} 표기를 써요:

my $smiley_from_name = "\N{WHITE SMILING FACE}";
my $smiley_from_code_point = "\N{U+263a}";

마찬가지로 정규식 리터럴에서도 쓸 수 있어요:

$smiley =~ /\N{WHITE SMILING FACE}/;
$smiley =~ /\N{U+263a}/;

또는 v5.32부터:

$smiley =~ /\p{Name=WHITE SMILING FACE}/;
$smiley =~ /\p{Name=whitesmilingface}/;

런타임에는:

use charnames ();
my $hebrew_alef_from_name
                     = charnames::string_vianame("HEBREW LETTER ALEF");
my $hebrew_alef_from_code_point = charnames::string_vianame("U+05D0");

당연히 ord()가 그 반대를 해요 — 문자를 코드 포인트로 바꿔줘요.

다른 런타임 옵션도 있어요. pack()을 쓸 수도 있고:

my $hebrew_alef_from_code_point = pack("U", 0x05d0);

chr()을 쓸 수도 있는데, 일반적인 경우엔 덜 편리해요:

$hebrew_alef_from_code_point = chr(utf8::unicode_to_native(0x05d0));
utf8::upgrade($hebrew_alef_from_code_point);

인자가 0xFF 위면 utf8::unicode_to_native()utf8::upgrade()가 필요 없어서, 위는 이렇게 쓸 수도 있어요:

$hebrew_alef_from_code_point = chr(0x05d0);

0x5d0이 255 위니까요.

\x{}\o{}도 큰따옴표 문자열에서 컴파일 시점에 코드 포인트를 지정하는 데 쓸 수 있어요. 다만 옛 Perl과의 역호환성을 위해, 256 미만 코드 포인트에 대해서는 chr()과 같은 규칙이 적용돼요.

utf8::unicode_to_native()는 Perl 코드가 EBCDIC 플랫폼으로 이식 가능하도록 쓰여요. 코드를 non-ASCII 플랫폼에서 쓸 사람이 절대 없을 거라 확신한다면 생략해도 돼요. Perl v5.22부터는 ASCII 플랫폼에서 이 호출이 최적화돼 사라지므로, 추가해도 성능 손실이 전혀 없어요. 아니면 그걸 필요로 하지 않는 다른 구문을 써도 되고요.

이 모든 이름과 숫자 코드를 찾는 방법은 "더 자세한 자료"를 참고하세요.

이전 릴리스 주의사항 (Earlier releases caveats)

  • EBCDIC 플랫폼에서 v5.22 이전에는 \N{U+...}가 제대로 동작하지 않아요.
  • v5.16 이전에는 문자 이름으로 \N{...}를 쓰려면(U+... 코드 포인트가 아닌) use charnames :full이 필요했어요.
  • v5.14 이전에는 문자 이름으로 \N{...}를 쓸 때 버그가 좀 있었어요.
  • charnames::string_vianame()은 v5.14에 도입됐어요. 그 전에는 charnames::vianame()이 동작하지만, 인자가 "U+..." 형태일 때만이에요. 런타임에 문자 이름으로 Unicode를 얻는 가장 좋은 방법은 아마:
use charnames ();
my $hebrew_alef_from_name
                 = pack("U", charnames::vianame("HEBREW LETTER ALEF"));

Unicode 다루기 (Handling Unicode)

Unicode 다루기는 대부분 투명해요 — 그냥 평소처럼 문자열을 쓰면 돼요. index(), length(), substr() 같은 함수는 Unicode 문자에 대해 동작하고, 정규식도 Unicode 문자에 대해 동작해요 (perlunicode와 perlretut 참고).

참고로 Perl은 grapheme cluster를 별개의 문자로 여겨요. 예를 들어

print length("\N{LATIN CAPITAL LETTER A}\N{COMBINING ACUTE ACCENT}"),
      "\n";

는 1이 아니라 2를 출력해요. 유일한 예외는 정규식에 확장 grapheme cluster를 매치하는 \X가 있다는 거예요. (그래서 정규식의 \X는 예시 두 문자 시퀀스 전체를 매치해요.)

하지만 레거시 인코딩, I/O, 특정 특수 경우를 다룰 때는 그렇게 투명하지 않아요.

레거시 인코딩 (Legacy Encodings)

레거시 데이터와 Unicode를 결합하면 레거시 데이터를 Unicode로 업그레이드해야 해요. 보통 레거시 데이터는 ISO 8859-1(또는 해당되면 EBCDIC)로 가정돼요.

Encode 모듈은 많은 인코딩을 알고 있고, 그 인코딩 사이의 변환을 위한 인터페이스를 제공해요:

use Encode 'decode';
$data = decode("iso-8859-3", $data); # convert from legacy

Unicode I/O

보통 Unicode 데이터를 쓰면

print FH $some_string_with_unicode, "\n";

Perl이 내부적으로 Unicode 문자열을 인코딩하는 데 쓰는 원시 바이트를 만들어내요. Perl의 내부 인코딩은 시스템과 그때 문자열에 어떤 문자가 있느냐에 달려 있어요. 문자가 0x100 이상 코드 포인트에 있으면 경고가 나요. 원하는 인코딩으로 출력이 명시적으로 렌더링되게 하고 — 경고를 피하려면 — 원하는 인코딩으로 스트림을 여세요. 예시들:

open FH, ">:utf8", "file";

open FH, ">:encoding(ucs2)",      "file";
open FH, ">:encoding(UTF-8)",     "file";
open FH, ">:encoding(shift_jis)", "file";

이미 열린 스트림에는 binmode()를 쓰세요:

binmode(STDOUT, ":utf8");

binmode(STDOUT, ":encoding(ucs2)");
binmode(STDOUT, ":encoding(UTF-8)");
binmode(STDOUT, ":encoding(shift_jis)");

인코딩 이름 매칭은 느슨해요 — 대소문자는 무시되고, 많은 인코딩에 별칭이 여럿 있어요. 참고로 :utf8 레이어는 항상 정확히 그렇게 지정해야 해요. 인코딩 이름의 느슨한 매칭 대상이 아니거든요. 또 현재 :utf8입력에 안전하지 않아요. 데이터가 실제로 유효한 UTF-8인지 검증하지 않고 받아들이기 때문이에요. 대신 :encoding(UTF-8)(하이픈 있든 없든)을 써야 해요.

:utf8 레이어는 PerlIO, :encoding() 레이어는 PerlIO::encoding과 Encode::PerlIO, Encode 모듈이 지원하는 많은 인코딩은 Encode::Supported를 참고하세요.

파일이 Unicode 또는 레거시 인코딩 중 하나로 인코딩돼 있다고 아는 파일을 읽는다고 Perl의 눈에는 마법처럼 데이터가 Unicode로 바뀌진 않아요. 그러려면 파일을 열 때 적절한 레이어를 지정하세요:

open(my $fh,'<:encoding(UTF-8)', 'anything');
my $line_of_unicode = <$fh>;

open(my $fh,'<:encoding(Big5)', 'anything');
my $line_of_unicode = <$fh>;

I/O 레이어는 open pragma로 더 유연하게 지정할 수도 있어요. open 참고, 또는 다음 예시를 보세요:

use open ':encoding(UTF-8)'; # input/output default encoding will be
                             # UTF-8
open X, ">file";
print X chr(0x100), "\n";
close X;
open Y, "<file";
printf "%#x\n", ord(<Y>); # this should print 0x100
close Y;

open pragma로 :locale 레이어를 쓸 수 있어요:

BEGIN { $ENV{LC_ALL} = $ENV{LANG} = 'ru_RU.KOI8-R' }
# the :locale will probe the locale environment variables like
# LC_ALL
use open OUT => ':locale'; # russki parusski
open(O, ">koi8");
print O chr(0x430); # Unicode CYRILLIC SMALL LETTER A = KOI8-R 0xc1
close O;
open(I, "<koi8");
printf "%#x\n", ord(<I>), "\n"; # this should print 0xc1
close I;

이 방법들은 I/O 스트림에 스트림에서 읽을 때 지정된 인코딩의 데이터를 변환하는 투명한 필터를 설치해요. 결과는 항상 Unicode예요.

open pragma는 pragma 뒤의 모든 open() 호출에 기본 레이어를 설정해 영향을 줘요. 특정 스트림에만 영향을 주고 싶다면 open() 호출에 명시적 레이어를 직접 쓰세요.

이미 열린 스트림의 인코딩은 binmode()로 바꿀 수 있어요 (perlfunc의 "binmode" 참고).

:locale은 현재 open()binmode()에선 동작하지 않고 open pragma에서만 동작해요. :utf8:encoding(...)open(), binmode(), open pragma 모두에서 동작해요.

마찬가지로 이 I/O 레이어를 출력 스트림에 써서, 스트림에 쓸 때 Unicode를 지정된 인코딩으로 자동 변환할 수 있어요. 예를 들어 다음 스니펫은 ISO-2022-JP(일명 JIS)로 인코딩된 "text.jis" 파일의 내용을 UTF-8로 인코딩된 "text.utf8" 파일로 복사해요:

open(my $nihongo, '<:encoding(iso-2022-jp)', 'text.jis');
open(my $unicode, '>:utf8',                  'text.utf8');
while (<$nihongo>) { print $unicode $_ }

open()open pragma 모두 인코딩 이름을 유연하게 허용해요 — koi8-rKOI8R 모두 이해돼요.

ISO, MIME, IANA 및 기타 표준화 기구가 인식하는 일반적인 인코딩이 인식돼요. 더 자세한 목록은 Encode::Supported를 참고하세요.

read()는 문자를 읽고 문자 수를 반환해요. seek()tell()sysseek()처럼 바이트 수로 동작해요.

sysread()syswrite()는 문자 인코딩 레이어가 있는 파일핸들에 쓰면 안 돼요. 제대로 동작하지 않으며, 그런 동작은 perl 5.24부터 deprecated됐어요.

입력 시 기본 레이어가 없으면 아무 변환도 하지 않는 기본 동작 때문에, 파일을 인코딩 데이터로 계속 확장하는 코드를 실수로 쓰기 쉬워요:

# BAD CODE WARNING
open F, "file";
local $/; ## read in the whole file of 8-bit characters
$t = <F>;
close F;
open F, ">:encoding(UTF-8)", "file";
print F $t; ## convert to UTF-8 on output
close F;

이 코드를 두 번 실행하면 파일 내용이 두 번 UTF-8 인코딩돼요. use open ':encoding(UTF-8)'이 이 버그를 피했을 거예요. 아니면 입력 파일도 명시적으로 UTF-8로 여는 방법도 있겠죠.

참고: :utf8:encoding 기능은 Perl이 PerlIO로 빌드됐을 때만 동작해요. 대부분 시스템에서 기본이에요.

Unicode를 텍스트로 표시하기 (Displaying Unicode As Text)

때로 Unicode를 담은 Perl 스칼라를 단순 ASCII(또는 EBCDIC) 텍스트로 표시하고 싶을 수 있어요. 다음 서브루틴은 인자를 변환해서, 코드 포인트가 255보다 큰 Unicode 문자는 \x{...}로, 제어 문자(예: \n)는 \x..로, 나머지는 그대로 표시해요:

sub nice_string {
       join("",
       map { $_ > 255                    # if wide character...
             ? sprintf("\\x{%04X}", $_)  # \x{...}
             : chr($_) =~ /[[:cntrl:]]/  # else if control character...
               ? sprintf("\\x%02X", $_)  # \x..
               : quotemeta(chr($_))      # else quoted or as themselves
       } unpack("W*", $_[0]));           # unpack Unicode characters
  }

예를 들어

nice_string("foo\x{100}bar\n")

는 문자열

'foo\x{0100}bar\x0A'

를 반환해서 바로 출력할 준비가 돼요.

(\\x{}\\N{} 대신 여기 쓰인 이유는, 네이티브 값이 무엇인지 보길 원할 가능성이 가장 높기 때문이에요.)

특수 경우 (Special Cases)

  • Perl 5.28부터 ~ 같은 비트 연산자가 255를 넘는 코드 포인트를 담은 문자열에 동작하는 것은 불법이에요.

  • vec() 함수는 서수 값이 255를 넘는 문자를 담은 문자열에 쓰면 놀라운 결과를 낼 수 있어요. 그런 경우 결과는 문자의 내부 인코딩과는 일관되지만 다른 것과는 일치하지 않아요. 그러니 하지 마세요. Perl 5.28부터 하면 deprecation 메시지가 나오고, 5.32에서 불법이 돼요.

  • Perl 내부 인코딩 엿보기 — 일반 Perl 사용자는 Perl이 특정 Unicode 문자열을 어떻게 인코딩하는지 신경 써선 안 돼요 (Unicode가 든 문자열 내용에 접근하는 정상적인 방법 — 입출력 — 은 항상 명시적으로 정의된 I/O 레이어를 통해야 하니까요). 하지만 꼭 해야 한다면, 배후를 들여다보는 두 가지 방법이 있어요.

    하나는 unpack("C*", ...)로 문자열 인코딩이 무엇이든 그 바이트를 얻는 거고, unpack("U0..", ...)로 UTF-8 인코딩의 바이트를 얻는 거예요:

# this prints  c4 80  for the UTF-8 bytes 0xc4 0x80
print join(" ", unpack("U0(H2)*", pack("U", 0x100))), "\n";

또 하나는 Devel::Peek 모듈을 쓰는 거예요:

perl -MDevel::Peek -e 'Dump(chr(0x100))'

이는 FLAGS의 UTF8 플래그와 PV의 UTF-8 바이트·Unicode 문자를 모두 보여줘요. 아래 utf8::is_utf8() 함수에 대한 논의도 참고하세요.

고급 주제 (Advanced Topics)

  • 문자열 동등성 (String Equivalence) — Unicode에서 문자열 동등성 문제는 좀 복잡해져요. "같다"는 게 무슨 뜻이죠?

    (LATIN CAPITAL LETTER A WITH ACUTELATIN CAPITAL LETTER A와 같은가?)

    짧은 답: 기본적으로 Perl은 문자 코드 포인트만으로 동등성(eq, ne)을 비교해요. 위 경우 답은 아니요(0x00C1 != 0x0041이니까요). 하지만 때로는 모든 "CAPITAL LETTER A"가 같다고 여겨야 할 수도 있고, 아무 대소문자 A까지 같다고 여겨야 할 수도 있어요.

    긴 답: 문자 정규화와 대소문자 이슈를 고려해야 해요: Unicode::Normalize, Unicode Technical Report #15(Unicode Normalization Forms), Unicode 표준의 대소문자 매핑 섹션 참고.

    Perl 5.8.0 기준으로 Case Mappings/SpecialCasing의 "Full" case-folding이 구현됐지만, qr//i에 그들과 관련된 버그가 남아 있다가 5.14에서 대부분, 5.18에서 사실상 전부 고쳐졌어요.

  • 문자열 정렬 (String Collation) — 사람들은 자기 문자열이 보기 좋게 정렬되길 원해요. Unicode 용어로는 collation이죠. 그런데 다시, collate가 무슨 뜻일까요?

    (LATIN CAPITAL LETTER A WITH ACUTELATIN CAPITAL LETTER A WITH GRAVE보다 먼저 오나요, 나중에 오나요?)

    짧은 답: 기본적으로 Perl은 문자 코드 포인트만으로 문자열을 비교해요(lt, le, cmp, ge, gt). 위 경우 답은 "after"예요. 0x00C1 > 0x00C0이니까요.

    긴 답: "그때그때 다르다"이고, (적어도) 언어 문맥을 알지 못하면 좋은 답을 줄 수 없어요. Unicode::Collate와 Unicode Collation Algorithm https://www.unicode.org/reports/tr10/ 참고.

잡다한 것 (Miscellaneous)

  • 문자 범위와 클래스 (Character Ranges and Classes) — 정규식의 대괄호 문자 클래스(예: /[a-z]/)와 tr///(일명 y///) 연산자의 문자 범위는 마법처럼 Unicode 인식이 아니에요. 즉 [A-Za-z]가 마법처럼 "모든 알파벳 문자"를 뜻하진 않아요 (8비트 문자에서도 그런 뜻은 아니었어요. 8비트라면 locale을 쓸 때(perllocale) /[[:alpha:]]/를, 아니면 8비트 인식 속성 \p{alpha}를 쓰세요).

    \p(와 그 역 \P)로 시작하는 모든 속성은 실제로 Unicode 인식 문자 클래스예요. 수십 개가 있는데, perluniprops를 참고하세요.

    v5.22부터 정규식 패턴 문자 범위의 끝점으로 Unicode 코드 포인트를 쓸 수 있고, 그 범위는 두 끝점 사이의 모든 Unicode 코드 포인트를 포함해요:

qr/ [ \N{U+03} - \N{U+20} ] /xx

\N{U+03}, \N{U+04}, ..., \N{U+20} 코드 포인트를 포함해요.

Perl v5.24부터 tr/// 범위에서도 동작해요.

  • 문자열-숫자 변환 (String-To-Number Conversions) — Unicode는 익숙한 0부터 9 외에도 아랍·인도 숫자 같은 여러 다른 십진·숫자 문자를 정의해요. Perl은 ASCII 09(그리고 16진수용 ASCII af) 외의 숫자에 대한 문자열-숫자 변환을 지원하지 않아요. 어떤 Unicode 문자열에서든 안전한 변환을 원한다면 Unicode::UCD의 "num()"을 쓰세요.

질문과 답 (Questions With Answers)

  • 옛 스크립트가 깨질까요? — 아마 아닐 거예요. Unicode 문자를 어떻게든 만들어내지 않는 한, 옛 동작은 보존돼요. 바뀌고 Unicode를 만들 수 있게 된 거의 유일한 동작은 옛 chr() 동작이에요. 255보다 큰 인자를 주면 255로 나눈 나머지 문자를 만들어냈거든요. 예를 들어 chr(300)은 예전엔 chr(45) 즉 "-"(ASCII)였는데, 이제는 LATIN CAPITAL LETTER I WITH BREVE예요.

  • 스크립트가 Unicode와 동작하게 하려면 어떻게 하나요? — Unicode 데이터를 만들기 전까지는 아무것도 바뀌지 않으므로 일할 것이 거의 없어요. 가장 중요한 건 입력을 Unicode로 받는 거예요. 그건 위 I/O 논의를 보세요. 완전한 원활한 Unicode 지원을 원하면 use feature 'unicode_strings'(또는 use v5.12 이상)를 스크립트에 추가하세요.

  • 문자열이 Unicode인지 어떻게 알 수 있나요? — 신경 쓸 필요가 없어야 해요. 하지만 Perl이 5.14.0 이전이거나 use feature 'unicode_strings'use 5.012 이상을 지정하지 않았다면 신경 써야 할 수 있어요. 그 경우 128~255 범위 코드 포인트의 규칙이, 그들이 담긴 문자열이 Unicode인지 아닌지에 따라 다르기 때문이에요 (perlunicode의 "When Unicode Does Not Happen" 참고).

    문자열이 Unicode인지 판별하려면:

print utf8::is_utf8($string) ? 1 : 0, "\n";

하지만 이는 문자열의 어떤 문자가 반드시 UTF-8 인코딩됐다는 뜻도, 어떤 문자의 코드 포인트가 0xFF(255)나 심지어 0x80(128)보다 크다는 뜻도, 문자열에 문자가 아예 있다는 뜻도 아니에요. is_utf8()이 하는 일은 $string에 붙은 내부 "utf8ness" 플래그 값을 반환하는 것뿐이에요. 플래그가 꺼져 있으면 스칼라의 바이트를 단일 바이트 인코딩으로 해석하고, 켜져 있으면 스칼라의 바이트를 (가변 길이·잠재적 멀티바이트) UTF-8 인코딩된 문자 코드 포인트로 해석해요. UTF-8 문자열에 더해지는 바이트는 자동으로 UTF-8로 업그레이드돼요. 비UTF-8과 UTF-8 스칼라가 섞여 병합되면(큰따옴표 보간, 명시적 결합, printf/sprintf 매개변수 치환) 바이트 문자열 복사본이 UTF-8로 업그레이드된 것처럼 결과가 UTF-8 인코딩돼요. 예를 들어:

$x = "ab\x80c";
$y = "\x{100}";
print "$x = $y\n";

출력 문자열은 UTF-8 인코딩된 ab\x80c = \x{100}\n이 되지만, $x는 바이트 인코딩으로 남아요.

때로는 문자 길이 대신 문자열의 바이트 길이를 정말 알아야 할 수 있어요. 그럴 땐 bytes pragma와 length() 함수를 쓰세요:

my $unicode = chr(0x100);
print length($unicode), "\n"; # will print 1
use bytes;
print length($unicode), "\n"; # will print 2
                              # (the 0xC4 0x80 of the UTF-8)
no bytes;
  • 파일이 어떤 인코딩인지 어떻게 알아내나요?Encode::Guess를 시도해 볼 수 있지만, 한계가 몇 가지 있어요.

  • 특정 인코딩에서 유효하지 않은 데이터는 어떻게 감지하나요?Encode 패키지로 변환을 시도해 보세요. 예를 들어:

use Encode 'decode';

if (eval { decode('UTF-8', $string, Encode::FB_CROAK); 1 }) {
    # $string is valid UTF-8
} else {
    # $string is not valid UTF-8
}

또는 unpack으로 디코딩을 시도해 보세요:

use warnings;
@chars = unpack("C0U*", $string_of_bytes_that_I_think_is_utf8);

유효하지 않으면 "Malformed UTF-8 character" 경고가 나와요. C0은 "문자별로 문자열을 처리하라"는 뜻이에요. 그게 없으면 unpack("U*", ...)는 U0 모드(형식 문자열이 U로 시작하면 기본)로 동작해 대상 문자열의 UTF-8 인코딩을 구성하는 바이트를 반환하는데, 그건 항상 동작하거든요.

  • 이진 데이터를 특정 인코딩으로(또는 그 반대로) 어떻게 변환하나요? — 이건 생각보다 유용하지 않을 거예요. 보통은 필요가 없어야 해요.

    한 가지 의미로, 묻는 게 그다지 말이 안 돼요. 인코딩은 문자를 위한 것이고 이진 데이터는 "문자"가 아니에요. 그래서 이진 데이터가 어떤 문자 집합과 인코딩인지 알지 못하면 "데이터"를 어떤 인코딩으로 변환한다는 건 의미가 없어요. 그걸 알면 더 이상 이진 데이터가 아니잖아요?

    특정 인코딩으로 해석해야 한다고 아는 원시 바이트 시퀀스가 있다면 Encode를 쓸 수 있어요:

use Encode 'from_to';
from_to($data, "iso-8859-1", "UTF-8"); # from latin-1 to UTF-8

from_to() 호출은 $data의 바이트를 바꾸지만, Perl 입장에서 문자열 본질에는 실질적으로 바뀐 것이 없어요. 호출 전후 모두 $data 문자열은 그냥 8비트 바이트 묶음이에요. Perl 입장에서 문자열의 인코딩은 계속 "시스템 네이티브 8비트 바이트"로 남아요.

가상의 'Translate' 모듈에 비유할 수 있어요:

use Translate;
my $phrase = "Yes";
Translate::from_to($phrase, 'english', 'deutsch');
## phrase now contains "Ja"

문자열 내용은 바뀌지만 문자열 본질은 안 바뀌어요. 호출 후에도 Perl은 전보다 문자열 내용이 긍정을 뜻하는지 더 알지 못해요.

데이터 변환으로 돌아와서, 시스템 네이티브 8비트 인코딩(예: Latin-1, EBCDIC)의 데이터가 있거나 원한다면 pack/unpack으로 Unicode와 변환할 수 있어요:

$native_string  = pack("W*", unpack("U*", $Unicode_string));
$Unicode_string = pack("U*", unpack("W*", $native_string));

유효한 UTF-8이라고 아는 바이트 시퀀스가 있는데 Perl이 아직 모른다면, Perl을 믿게 만들 수도 있어요:

$Unicode = $bytes;
utf8::decode($Unicode);

또는:

$Unicode = pack("U0a*", $bytes);

UTF-8 시퀀스를 구성하는 바이트는 이렇게 찾을 수 있고:

@bytes = unpack("C*", $Unicode_string)

잘 짜인 Unicode는 이렇게 만들 수 있어요:

$Unicode_string = pack("U*", 0xff, ...)
  • Unicode를 어떻게 표시하고 입력하나요?http://www.alanwood.net/unicode/http://www.cl.cam.ac.uk/~mgk25/unicode.html 참고.

  • Unicode는 전통적인 locale과 어떻게 동작하나요? — locale이 UTF-8 locale이라면, Perl v5.26부터 Perl이 모든 카테고리에서 잘 동작해요. 그 전에는 Perl v5.20부터 정렬과 cmp 연산자를 다루는 LC_COLLATE를 제외한 모든 카테고리에서 동작했어요. 하지만 표준 Unicode::CollateUnicode::Collate::Locale 모듈이 정렬 문제에 훨씬 더 강력한 해결책을 제공하고 이전 릴리스에서도 동작한다는 걸 기억하세요.

    다른 locale에 대해서는 Perl 5.16부터

use locale ':not_characters';

을 지정해서 Perl이 그들과 잘 동작하게 할 수 있어요. 단점은 locale 문자 집합에서/로 스스로 변환해야 한다는 거예요. 위 "Unicode I/O"에서

use open ':locale';

을 어떻게 쓰는지 봤지만, 전체 세부사항(그걸 지정하지 않을 때 생기는 함정 포함)은 perllocale의 "Unicode and UTF-8"에 있어요.

16진수 표기 (Hexadecimal Notation)

Unicode 표준은 16진수 표기를 선호해요. Unicode를 256문자 블록으로 나눈 게 더 분명히 드러나거든요. 16진수는 십진수보다 단순히 더 짧기도 해요. 십진수 표기도 쓸 수 있지만, Unicode 표준과 일하는 데는 16진수를 배우는 게 그냥 인생을 더 쉽게 만들어요. U+HHHH 표기는 16진수를 써요.

0x 접두사는 16진수를 뜻하고, 자릿수는 0-9와 a-f(또는 A-F, 대소문자 무관)예요. 16진수 자릿수 하나는 4비트, 즉 반 바이트를 나타내요. print 0x..., "\n"은 16진수를 십진수로 보여주고, printf "%x\n", $decimal은 십진수를 16진수로 보여줘요. 16진수의 "16진 자릿수"만 있다면 hex() 함수를 쓸 수 있어요.

print 0x0009, "\n";    # 9
print 0x000a, "\n";    # 10
print 0x000f, "\n";    # 15
print 0x0010, "\n";    # 16
print 0x0011, "\n";    # 17
print 0x0100, "\n";    # 256

print 0x0041, "\n";    # 65

printf "%x\n",  65;    # 41
printf "%#x\n", 65;    # 0x41

print hex("41"), "\n"; # 65

더 자세한 자료 (Further Resources)

옛 Perl의 Unicode (UNICODE IN OLDER PERLS)

Perl을 5.8.0 이상으로 업그레이드할 수 없다면, CPAN에서 구할 수 있는 Unicode::String, Unicode::Map8, Unicode::Map 모듈로 여전히 Unicode 처리를 어느 정도 할 수 있어요. GNU recode가 설치돼 있다면 문자 변환에 Perl 프론트엔드 Convert::Recode를 쓸 수도 있어요.

다음은 ISO 8859-1(Latin-1) 바이트를 UTF-8 바이트로, 그리고 다시 돌아오는 빠른 변환이에요. 이 코드는 더 오래된 Perl 5 버전에서도 동작해요:

# ISO 8859-1 to UTF-8
s/([\x80-\xFF])/chr(0xC0|ord($1)>>6).chr(0x80|ord($1)&0x3F)/eg;

# UTF-8 to ISO 8859-1
s/([\xC2\xC3])([\x80-\xBF])/chr(ord($1)<<6&0xC0|ord($2)&0x3F)/eg;

함께 보기 (SEE ALSO)

perlunitut, perlunicode, Encode, open, utf8, bytes, perlretut, perlrun, Unicode::Collate, Unicode::Normalize, Unicode::UCD

Copyright 2001-2011 Jarkko Hietaniemi <[email protected]>. 현재 Perl 5 Porters가 유지보수해요. 이 문서는 Perl 자체와 같은 조건으로 배포될 수 있어요.

더 알아보기 (Learn more)

  • perlunicode — Perl의 Unicode 지원 상세
  • perlunitut — Unicode 튜토리얼
  • Encode — 인코딩 변환