Perl의 유니코드 지원

Perl의 유니코드 지원 (perlunicode)

이 문서는 Perl이 유니코드(Unicode)를 어떻게 지원하는지 설명해요. 아직 이 문서를 읽기 전에 perlunitutperluniintro에 먼저 익숙해지는 걸 권장합니다.

유니코드는 세계 모든 문자 집합의 인코딩을 하나의 표준으로 통일(UNI-fy) 하려는 목표를 가져요. 유니코드가 처음 만들어졌을 당시 존재하던 여러 코딩 표준들에서 각 표준을 유니코드로 변환하는 것은 본질적으로 원래 표준의 각 코드 포인트에 상수(constant)를 더하는 것과 같았어요. 다시 되돌리는 것은 그 같은 상수를 빼 주는 것뿐이었고요. ASCII와 ISO-8859-1의 경우 그 상수는 0이에요. ISO-8859-5(키릴 문자)는 864, 히브리어(ISO-8859-8)는 1488, 태국어(ISO-8859-11)는 3424, 이런 식이죠. 이 덕분에 변환이 쉬워졌고, 유니코드의 채택도 촉진됐어요.

그리고 실제로 잘 작동했어요. 오늘날 그런 레거시 표준들은 거의 쓰이지 않아요. 대부분 유니코드를 사용하죠.

유니코드는 포괄적인 표준이에요. Perl의 범위 밖에 있는 많은 것을 지정하는데, 예를 들어 문자 시퀀스를 어떻게 표시할지 같은 것도 포함돼요. 유니코드의 모든 측면에 대한 전체 논의는 https://www.unicode.org를 참고하세요.

출처: perlunicode - Unicode support in Perl

중요한 주의사항 (Important Caveats)

이 절의 일부는 처음 읽는 이에게는 이해가 안 될 수 있지만, 더 깊이 들어가기 전에 몇 가지 함정을 짚어 두는 게 중요하다고 생각해요. 이런 게 있어요:

유니코드 지원은 방대한 요구사항이에요. Perl이 유니코드 표준이나 그에 딸린 기술 보고서를 처음부터 끝까지 구현하지는 않지만, 많은 유니코드 기능을 지원합니다.

또한 유니코드 사용은 명확하지 않은 보안 문제를 제기할 수 있어요. 아래의 '유니코드의 보안 함의'를 보세요.

use feature 'unicode_strings'가 가장 안전해요

하위 호환성을 지키기 위해 Perl은 use feature 'unicode_strings' 프라그마가 지정되지 않는 한 완전한 내부 유니코드 지원을 켜지 않아요. (이것은 use v5.12 이상을 사용하면 자동으로 선택돼요.) 이렇게 하지 않으면 예상치 못한 놀라움을 마주할 수 있어요. 아래의 '"유니코드 버그"'를 보세요.

이 프라그마는 I/O에 영향을 주지 않아요. 문자열의 내부 표현도 바꾸지 않고 해석(interpretation)만 바꿔요. 유니코드가 완전히 지원되지 않는 곳이 여전히 몇 군데 있는데, 예를 들어 파일 이름 같은 데서요.

입력·출력 레이어 (Input and Output Layers)

지정한 인코딩으로 파일 핸들에 읽고 쓰려면 :encoding(...) 레이어를 사용하세요. (open 참고.)

비-ASCII, 비-UTF-8 Perl 스크립트를 UTF-8로 변환해야 해요

encoding 모듈은 perl 5.18부터 폐기(deprecated)됐고, 그 모듈이 필요로 하는 perl 내부는 5.26에서 제거됐어요.

use utf8는 스크립트에서 UTF-8을 활성화하는 데 여전히 필요해요

Perl 스크립트 자체가 UTF-8로 인코딩돼 있다면, 그걸(문자열이나 정규식 리터럴, 또는 식별자 이름 안에서) 인식하도록 use utf8 프라그마를 명시적으로 포함해야 해요. 이것이 명시적 use utf8이 필요한 유일한 경우예요. (utf8 참고.)

Perl 스크립트가 유니코드 BOM(BYTE ORDER MARK, '유니코드 인코딩' 참고)의 UTF-8 인코딩을 이루는 바이트들로 시작한다면, 그 바이트들은 완전히 무시돼요.

UTF-16 스크립트 자동 감지

Perl 스크립트가 유니코드 BOM(UTF-16LE, UTF-16BE)으로 시작하거나, 어느 엔디언이든 BOM 표시가 없는 UTF-16처럼 보이면, Perl은 그 스크립트를 적절한 유니코드 인코딩으로 올바르게 읽어 들여요.

바이트 의미론과 문자 의미론 (Byte and Character Semantics)

유니코드 이전에는 대부분의 인코딩이 각 문자를 인코딩하는 데 8비트(단일 바이트)를 사용했어요. 그래서 문자는 곧 바이트였고, 바이트는 곧 문자였으며, 가능한 문자는 256개 이하뿐이었어요. 이 절 제목의 '바이트 의미론(Byte Semantics)'이 이 동작을 말해요. 'Byte'와 'Character'를 구분할 필요가 없었죠.

그런데 유니코드가 등장하면서 백만 개가 넘는 문자(Perl은 그보다 더 많은 것도 허용해요)를 담을 공간이 생겼어요. 이는 문자를 표현하는 데 단일 바이트 이상이 필요할 수 있다는 뜻이고, 그래서 두 용어는 더 이상 동등하지 않게 됐어요. 중요한 것은 완전한 개체로서의 문자들이지, 보통 그것을 구성하는 바이트들이 아니에요. 이 절 제목의 '문자 의미론(Character Semantics)'이 그걸 말해요.

Perl은 내부적으로 '바이트'와 '문자'를 분리하도록 바뀌어야 했어요. 여러분도 아직 안 했다면 생각을 바꾸는 게 중요해요. 이제 여러분의 머릿속에서도 'byte'와 'character'가 더 이상 같은 의미가 아니어야 하니까요.

Perl 문자열의 기본 구성 요소는 항상 '문자(character)'였어요. 변화는 본질적으로, 구현이 더 이상 문자를 항상 단일 바이트로 생각하지 않는다는 것으로 귀결돼요.

주목할 여러 가지가 있어요:

  • 이 절 제목에 나오는 "Byte Semantics"가 그렇고,
  • 여러분이 Perl과 유니코드 접근 방식에 이미 익숙하다면 이 절은 건너뛰어도 괜찮아요.

결론은, Perl은 항상 '문자 의미론'을 실천해 왔지만, 유니코드의 등장으로 그것이 이제 '바이트 의미론'과는 다르다는 거예요.

ASCII 규칙과 유니코드 규칙 (ASCII Rules versus Unicode Rules)

유니코드 이전에, 문자가 곧 바이트가 곧 문자였을 때, Perl은 ASCII가 정의한 128개 문자, 즉 코드 포인트 0부터 127까지만 알았어요(use locale을 쓰지 않는 한). 그 결과 코드 포인트 128부터 255는 할당되지 않은 채 남아, 프로그램이 원하는 용도로 쓸 수 있었어요. 그것들이 가진 의미론은 서수(ordinal number)뿐이고, 음이 아닌 문자 클래스 어디에도 속하지 않는다는 것뿐이에요. 예를 들어 \w와 일치하는 것으로는 간주되지 않지만 모두 \W와는 일치해요.

물론 유니코드는 그 각각의 코드 포인트(와 255보다 큰 것들)에 특정한 의미를 할당해요. 하위 호환성을 지키기 위해 Perl은 유니코드가 의도된 것이라는 표시가 있을 때만 유니코드 의미를 사용해요. 그렇지 않으면 비-ASCII 코드 포인트는 할당되지 않은 것처럼 계속 취급돼요.

문자열이 유니코드로 취급돼야 한다는 것을 Perl이 아는 방법은 다음과 같아요:

  • 문자열에 코드 포인트가 255보다 큰 문자가 있다면,
  • 어떤 유니코드 규칙이 문자에 적용됐다면(\d 같은 유니코드 인식 패턴, \X, \p{...}, \N{...} 등),
  • 사용자가 유니코드 의미를 명시적으로 요청했다면.

위의 모든 것은 use bytes의 스코프 안에서는 무시된다는 점을 주의하세요. 하지만 이 프라그마는 디버깅 목적으로만 써야 해요.

또한 플랫폼의 운영체제와의 일부 상호작용은 결코 유니코드 규칙을 사용하지 않아요.

유니코드 규칙이 적용될 때:

확장 자소 클러스터 (Extended Grapheme Clusters, 논리 문자)

H 같은 문자를 생각해 봐요. 그것은 주위에 다양한 표시와 함께 나타날 수 있어요. 예를 들어 급강세(acute accent), 곡절 부호(circumflex), 다양한 고리·원·화살표 등을 글자 위·아래·한쪽에 붙일 수 있죠. 세계 언어 사이에는 가능한 조합이 무수히 많아요. 조합의 수는 천문학적이라서, 각 조합마다 문자가 있으면 유니코드의 백만 개가 넘는 가능한 문자를 곧 고갈시키고 말 거예요. 그래서 유니코드는 다른 접근을 택했어요. 기본 H에 대한 문자 하나, 그리고 가능한 표시 각각에 대한 문자 하나가 있고, 이것들을 다양하게 조합해 최종 논리 문자를 얻는 방식이에요. 그래서 논리 문자(단일 문자처럼 보이는 것)는 하나 이상의 개별 문자들의 시퀀스가 될 수 있어요. 유니코드 표준은 이것을 '확장 자소 클러스터(extended grapheme cluster)'라고 부르는데(더 이상 잘 안 쓰이는 'grapheme cluster'의 개선판이에요), Perl은 \X 정규식 구성으로 그런 시퀀스 전체를 일치시키는 기능을 제공해요.

하지만 유니코드의 의도는 기존 문자 집합 표준과 관행을 통일하는 것이고, 여러 기존 표준에는 이런 조합 중 일부와 같은 의미를 가진 단일 문자가 있어요. 예를 들어 ISO-8859-1에는 그런 게 꽤 많죠. "LATIN CAPITAL LETTER E WITH ACUTE"는 유니코드가 등장했을 때 이미 그 표준에 있었어요. 그래서 유니코드는 그 단일 문자로 이것을 자신의 레퍼토리에 추가했어요. 하지만 이 문자는 유니코드가 "LATIN CAPITAL LETTER E" 문자 뒤에 "COMBINING ACUTE ACCENT" 문자가 이어지는 시퀀스와 동등하다고 간주해요.

"LATIN CAPITAL LETTER E WITH ACUTE"는 '선결합(pre-composed)' 문자라고 불러요. 그리고 'E'와 'COMBINING ACCENT'의 시퀀스와의 그 동등성은 '정규 동등성(canonical equivalence)'이라고 불러요. 모든 선결합 문자는 (동등한 시퀀스로의) 분해(decomposition)를 가진다고 여겨지고, 그 분해 타입도 정규(canonical)라고 불러요. 문자열은 가능한 한 선결합 문자로만 구성될 수도 있고, 완전히 분해된 문자로만 구성될 수도 있어요. 유니코드는 각각 'NFC(Normalization Form Composed)'와 'NFD(Normalization Form Decomposed)'라고 불러요. Unicode::Normalize 모듈에는 둘 사이를 변환하는 함수가 있어요. 문자열이 결합 문자와 분해 문자를 둘 다 가질 수도 있는데, 이 모듈을 사용해 전부 하나로 만들 수 있어요.

이런 동등한 형태 중 아무 것이나 문자열로 받을 수도 있어요. 현재 Perl 5에는 그 차이를 무시하는 것이 없어요. 그래서 특별히 처리해야 해요. 보통 조언은, 추가 처리를 하기 전에 입력을 NFD로 변환하라는 거예요.

자세한 정보는 http://unicode.org/reports/tr15/를 참고하세요.

유니코드 문자 속성 (Unicode Character Properties)

(Perl이 개별 코드 포인트들의 시퀀스를 하나의 논리 문자로 간주하는 유일한 경우는 위에 이미 언급한 \X 구성이에요. 따라서 이 논의에서 '문자'는 단일 유니코드 코드 포인트를 뜻해요.)

거의 모든 유니코드 문자 속성은 정규식에서 \p{} '일치하는 속성' 구성과 그 부정인 \P{} '일치하지 않는 속성' 구성을 사용해 접근할 수 있어요.

예를 들어 \p{Uppercase}는 유니코드 "Uppercase" 속성을 가진 단일 문자와 일치하고, \p{L}General_Category"L"(문자)인 속성과 일치해요('General_Category' 참고). 단일 문자 속성 이름에는 대괄호가 필요 없어서 \p{L}\pL과 동일해요.

더 형식적으로, \p{Uppercase}는 유니코드 Uppercase 속성 값이 True인 단일 문자와 일치하고, \P{Uppercase}는 그 값이 False인 문자와 일치해요. 이것들은 각각 \p{Uppercase=True}\p{Uppercase=False}로 쓸 수도 있었어요.

이 형식성이 필요한 때는 속성이 2진(binary)이 아닐 때예요. 즉 TrueFalse보다 더 많은 값을 취할 수 있을 때죠. 예를 들어 Bidi_Class 속성('양방향 문자 타입' 참고)은 Left, Right, Whitespace 등 여러 값을 취할 수 있어요. 이것을 일치시키려면 속성 이름(Bidi_Class)과 일치시킬 값(Left, Right 등)을 둘 다 지정해야 해요. 위 예제들처럼 두 구성 요소를 등호(또는 동등하게 콜론)로 구분해 \p{Bidi_Class: Left}처럼 쓰는 방식으로 이뤄져요.

모든 유니코드 정의 문자 속성은 \p{property=value}\p{property:value}의 복합 형태로 쓸 수 있어요. 하지만 Perl은 단일 형태로만 쓰이는 추가 속성 몇 가지와, 모든 2진 속성 및 아래에서 설명하는 특정 속성들에 대한 단일 형태 단축형도 제공해요. 이 경우 속성 이름과 등호 또는 콜론 구분자를 생략할 수 있어요.

대부분의 유니코드 문자 속성은 동의어(앨리어스)를 적어도 두 개 가져요. 하나는 타이핑하기 쉬운 짧은 형태, 다른 하나는 더 서술적이라 이해하기 쉬운 긴 형태예요. 그래서 위의 "L""Letter" 속성은 동일하며 서로 바꿔 쓸 수 있어요. 마찬가지로 "Upper""Uppercase"의 동의어이고, \p{Uppercase}\p{Upper}로 동일하게 쓸 수 있었어요. 또한 속성이 가질 수 있는 값들에도 보통 여러 동의어가 있어요. 2진 속성의 경우 "True""T", "Yes", "Y" 세 개의 동의어를 갖고, "False"는 대응되게 "F", "No", "N"을 가져요. 하지만 조심하세요. 한 속성의 값에 대한 짧은 형태가 다른 속성에서 똑같이 철자가 쓰인 짧은 형태와 같은 의미가 아닐 수 있어요. "General_Category" 속성에서는 "L""Letter"를 뜻하지만, Bidi_Class 속성에서는 "L""Left"를 뜻해요. 속성과 동의어의 전체 목록은 perluniprops에 있어요.

속성 이름과 값의 대문자/소문자 차이는 무관해요. 그래서 \p{Upper}\p{upper}나 심지어 \p{UpPeR}와 같은 의미예요. 비슷하게 단어 중간 어디에나 밑줄을 더하거나 뺄 수 있어서, 이것들도 \p{U_p_p_e_r}와 동일해요. 그리고 중괄호나 등호·콜론 구분자 같은 비-단어 문자 옆의 공백은 일반적으로 무관해서 \p{ Upper }\p{ Upper_case : Y }도 이와 동일해요. 실제로 공백뿐 아니라 하이픈까지 보통 어디든 더하거나 뺄 수 있어요. 그래서 \p{ Up-per case = Yes}조차 동일해요. 유니코드가 이 모든 것을 '느슨한 매칭(loose-matching)'이라고 불러요. name 속성은 몇 가지 특이한 이름 때문에 여기에 제약이 좀 있어요. 자세한 내용은 https://www.unicode.org/reports/tr44/tr44-24.html#UAX44-LM2를 참고하세요.

더 엄격한 매칭이 쓰이는 몇몇 곳은 숫자 중간, name 속성, 그리고 밑줄로 시작하거나 끝나는 Perl 확장 속성들이에요. 엄격한 매칭은 공백(비-단어 문자 옆이 아닌), 하이픈, 비-내부 밑줄을 신경 써요.

\p{}\P{} 둘 다에서 첫 번째 중괄호와 속성 이름 사이에 캐럿(^)을 넣어 부정을 사용할 수도 있어요: \p{^Tamil}\P{Tamil}과 같아요.

거의 모든 속성은 대소문자 무시 매칭에 면역이에요. 즉 /i 정규식 수정자를 추가해도 일치시키는 것이 바뀌지 않아요. 영향을 받는 집합이 두 개 있어요. 첫 번째 집합은 Uppercase_Letter, Lowercase_Letter, Titlecase_Letter로, /i 매칭 아래에서는 모두 Cased_Letter와 일치해요. 두 번째 집합은 Uppercase, Lowercase, Titlecase로, /i 매칭 아래에서는 모두 Cased와 일치해요. 이 집합에는 그 하위 집합인 PosixUpperPosixLower도 포함되며, 둘 다 /i 아래에서 PosixAlpha와 일치해요. (이 집합들의 차이는, 로마 숫자 같은 어떤 것들은 대문자·소문자 모두로 나와서 Cased지만 문자로는 간주되지 않아 Cased_Letter가 아니라는 점이에요.)

비-유니코드 코드 포인트에 대해 유니코드 속성을 일치시킬 때의 특별한 고려사항은 '유니코드 코드 포인트를 넘어서'를 보세요.

General_Category

모든 유니코드 문자에는 일반 범주(general category)가 할당돼 있는데, 이는 "가장 보통의 문자 분류"예요(https://www.unicode.org/reports/tr44에서 인용).

이것을 쓰는 복합 방식은 \p{General_Category=Number}(짧게 \p{gc:n})와 같아요. 하지만 Perl은 등호나 콜론 구분자까지의 모든 것을 생략하는 단축형을 제공해요. 그래서 그냥 \pN이라고 써도 돼요.

General Category 속성이 가질 수 있는 값의 짧은·긴 형태는 다음과 같아요:

Short       Long

L           Letter
LC, L&      Cased_Letter (즉: [\p{Ll}\p{Lu}\p{Lt}])
Lu          Uppercase_Letter
Ll          Lowercase_Letter
Lt          Titlecase_Letter
Lm          Modifier_Letter
Lo          Other_Letter

M           Mark
Mn          Nonspacing_Mark
Mc          Spacing_Mark
Me          Enclosing_Mark

N           Number
Nd          Decimal_Number (또한 Digit)
Nl          Letter_Number
No          Other_Number

P           Punctuation (또한 Punct)
Pc          Connector_Punctuation
Pd          Dash_Punctuation
Ps          Open_Punctuation
Pe          Close_Punctuation
Pi          Initial_Punctuation
            (용법에 따라 Ps나 Pe처럼 동작할 수 있음)
Pf          Final_Punctuation
            (용법에 따라 Ps나 Pe처럼 동작할 수 있음)
Po          Other_Punctuation

S           Symbol
Sm          Math_Symbol
Sc          Currency_Symbol
Sk          Modifier_Symbol
So          Other_Symbol

Z           Separator
Zs          Space_Separator
Zl          Line_Separator
Zp          Paragraph_Separator

C           Other
Cc          Control (또한 Cntrl)
Cf          Format
Cs          Surrogate
Co          Private_Use
Cn          Unassigned

단일 문자 속성은 같은 문자로 시작하는 두 글자 하위 속성 중 아무것과 일치하는 모든 문자와 일치해요. LCL&는 특별한데, 둘 다 Ll, Lu, Lt가 일치하는 모든 것의 집합에 대한 앨리어스예요.

양방향 문자 타입 (Bidirectional Character Types)

문자 체계는 방향성이 서로 다르기 때문에(예를 들어 히브리어와 아랍어는 오른쪽에서 왼쪽으로 써요) 유니코드는 Bidi_Class 속성을 제공해요. 이 속성이 가질 수 있는 값 중 일부는:

Value       Meaning

L           Left-to-Right (왼쪽에서 오른쪽)
LRE         Left-to-Right Embedding (왼쪽-오른쪽 삽입)
LRO         Left-to-Right Override (왼쪽-오른쪽 오버라이드)
R           Right-to-Left (오른쪽에서 왼쪽)
AL          Arabic Letter (아랍 문자)
RLE         Right-to-Left Embedding (오른쪽-왼쪽 삽입)
RLO         Right-to-Left Override (오른쪽-왼쪽 오버라이드)
PDF         Pop Directional Format (방향 형식 팝)
EN          European Number (유럽 숫자)
ES          European Separator (유럽 구분자)
ET          European Terminator (유럽 종결자)
AN          Arabic Number (아랍 숫자)
CS          Common Separator (공통 구분자)
NSM         Non-Spacing Mark (비-공백 마크)
BN          Boundary Neutral (경계 중립)
B           Paragraph Separator (문단 구분자)
S           Segment Separator (세그먼트 구분자)
WS          Whitespace (공백)
ON          Other Neutrals (기타 중립)

이 속성은 항상 복합 형태로 써져요. 예를 들어 \p{Bidi_Class:R}은 보통 오른쪽에서 왼쪽으로 쓰이는 문자와 일치해요. "General_Category" 속성과 달리 이 속성은 미래의 유니코드 릴리스에서 값이 더 추가될 수 있어요. 위에 나열된 것들이 많은 유니코드 릴리스에서 완전한 집합을 이뤘지만, 유니코드 6.3에서 다른 것들이 추가됐어요. 현재 것들이 무엇인지는 항상 perluniprops에서 찾을 수 있어요. 그리고 https://www.unicode.org/reports/tr9/에서 이것을 어떻게 쓰는지 설명해요.

문자 체계 (Scripts)

세계의 언어들은 여러 다른 문자 체계로 쓰여요. 이 문장(번역본으로 읽는 게 아니라면)은 라틴 문자로 쓰이고, 러시아어는 키릴 문자로, 그리스어는 그리스 문자로, 일본어는 주로 히라가나나 가타카나로 써요. 그 밖에도 훨씬 많죠.

유니코드 ScriptScript_Extensions 속성은 주어진 문자가 어떤 문자 체계에 있는지 알려줘요. Script_Extensions 속성은 Script의 개선판이에요. 아래에서 보여주죠. 두 속성 중 아무것도 복합 형태로 지정할 수 있어요. \p{Script=Hebrew}(짧게 \p{sc=hebr})나 \p{Script_Extensions=Javanese}(짧게 \p{scx=java})처럼요. 게다가 Perl은 모든 Script_Extensions 속성 이름에 대한 단축형을 제공해요. 등호(또는 콜론)까지의 모든 것을 생략하고 그냥 \p{Latin}이나 \P{Cyrillic}이라고 쓸 수 있어요. (Script는 그렇지 않고 복합 형태로 써야 해요. Perl v5.26 이전에는 단일 형태가 예전의 Script 버전을 반환했지만, Script_Extensions가 더 나은 결과를 주기 때문에 바뀌었어요.)

이 두 속성의 차이는 여러 문자 체계에서 쓰이는 문자와 관련돼 있어요. 예를 들어 숫자 '0'부터 '9'는 세계 여러 곳에서 쓰여요. 이것들은 Common이라는 문자 체계에 배치돼 있어요. 다른 문자들은 몇몇 문자 체계에서만 쓰여요. 예를 들어 "KATAKANA-HIRAGANA DOUBLE HYPHEN"은 일본어 두 문자 체계인 가타카나와 히라가나 모두에서 쓰이지만, 다른 곳에서는 쓰이지 않아요. Script 속성은 여러 문자 체계에서 쓰이는 모든 문자를 Common 문자 체계에 배치하는 반면, Script_Extensions 속성은 몇 개의 문자 체계에서만 쓰이는 것들을 각각의 그 문자 체계들에 배치해요. 그래도 많은 문자 체계에서 쓰이는 것들은 여전히 Common을 사용해요. 그래서 이 둘 모두가 일치해요:

"0" =~ /\p{sc=Common}/     # 일치함
"0" =~ /\p{scx=Common}/    # 일치함

그리고 다음 중 첫 번째만 일치해요:

"\N{KATAKANA-HIRAGANA DOUBLE HYPHEN}" =~ /\p{sc=Common}  # 일치함
"\N{KATAKANA-HIRAGANA DOUBLE HYPHEN}" =~ /\p{scx=Common} # 불일치

그리고 다음 중 마지막 두 개만 일치해요:

"\N{KATAKANA-HIRAGANA DOUBLE HYPHEN}" =~ /\p{sc=Hiragana}  # 불일치
"\N{KATAKANA-HIRAGANA DOUBLE HYPHEN}" =~ /\p{sc=Katakana}  # 불일치
"\N{KATAKANA-HIRAGANA DOUBLE HYPHEN}" =~ /\p{scx=Hiragana} # 일치함
"\N{KATAKANA-HIRAGANA DOUBLE HYPHEN}" =~ /\p{scx=Katakana} # 일치함

그래서 Script_Extensions는 개선된 Script로, Common 문자 체계에 있는 문자가 더 적고 그만큼 다른 문자 체계에 더 많아요. 이것은 유니코드 버전 6.0에서 새로 나온 것이고, 그 데이터는 사물이 정리되면서 이후 릴리스에서 크게 바뀔 가능성이 커요. 새 코드는 아마 그냥 Script가 아니라 Script_Extensions를 사용해야 해요. Script_Extensions가 없는 유니코드 릴리스로 perl을 컴파일하면, 단일 형태의 Perl 확장은 대신 그냥 Script 속성을 가리켜요. Script 속성이 없는 유니코드 버전으로 컴파일한다면, 이 확장들은 아예 정의되지 않아요.

(실제로 Common 외에, Inherited 문자 체계도 여러 문자 체계에서 쓰이는 문자들을 포함해요. 이것들은 제어 문자(controlling character)의 문자 체계 값을 상속받는 수식 문자들(modifier character)이에요. 이 중 일부는 많은 문자 체계에서 쓰여서 ScriptScript_Extensions 둘 다에서 Inherited로 들어가요. 다른 것들은 몇 개의 문자 체계에서만 쓰여서 Script에서는 Inherited지만 Script_Extensions에서는 그렇지 않아요.)

유니코드에는 0-9와 동등하고 정규식의 \d로 일치시킬 수 있는 서로 다른 숫자 집합들이 여러 개 있다는 점을 강조할 가치가 있어요. 단일 언어에서만 쓰이면 그 언어의 ScriptScript_Extensions에 속해요. 둘 이상의 문자 체계에서 쓰이면 sc=Common에 있을 거고, 많은 문자 체계에서 쓰일 때만 scx=Common에 있어야 해요.

위 설명은 일부 세부사항을 생략했어요. UAX#24 "Unicode Script Property"를 참고하세요: https://www.unicode.org/reports/tr24.

문자 체계와 단축형의 완전한 목록은 perluniprops에 있어요.

"Is" 접두사 사용 (Use of the "Is" Prefix)

하위 호환성(고대의 Perl 5.6을 위함)을 위해, 지금까지 언급한 복합 형태 없이 쓸 수 있는 모든 속성은 그 이름에 IsIs_를 앞에 붙일 수 있어요. 예를 들어 \P{Is_Lu}\P{Lu}와 같고, \p{IsScript:Arabic}\p{Arabic}과 같아요.

블록 (Blocks)

문자 체계 외에 유니코드는 문자 블록(block)도 정의해요. 문자 체계와 블록의 차이는, 문자 체계의 개념이 자연 언어에 더 가깝다는 점이고, 블록의 개념은 연속적인 서수 값을 가진 유니코드 문자 그룹에 기반한 좀 더 인위적인 그룹화라는 점이에요. 예를 들어 "Basic Latin" 블록은 서수가 0과 127 사이(포함)인 모든 문자, 즉 ASCII 문자들이에요. "Latin" 문자 체계는 이것의 일부 문자와 "Latin-1 Supplement", "Latin Extended-A" 같은 여러 다른 블록의 문자를 포함하지만, 그 블록들의 모든 문자를 포함하지는 않아요. 예를 들어 0-9 숫자는 포함하지 않아요. 그 숫자들이 여러 문자 체계에서 공유되기 때문에 Common 문자 체계에 있으니까요.

문자 체계와 블록에 대한 더 자세한 내용은 UAX#24 "Unicode Script Property"를 참고하세요: https://www.unicode.org/reports/tr24.

자연 언어를 처리할 때는 Script_ExtensionsScript 속성을 쓰고 싶을 가능성이 높아요. Block 속성은 유니코드의 세부 사항을 다룰 때 때때로 유용할 수 있어요.

블록 이름은 복합 형태로 일치시켜요. \p{Block: Arrows}\p{Blk=Hebrew}처럼요. 다른 대부분의 속성과 달리, 블록 이름 중 유니코드가 정의한 짧은 이름을 가진 것은 몇 개뿐이에요.

Perl은 블록 속성에 대해 다른 것과 충돌하지 않는 경우 단일 형태의 동의어도 정의해요. 하지만 이런 것은 쓰지 마세요. 불안정하니까요. 이것들은 Perl 확장이므로 공식 유니코드 속성 이름에 종속돼요. 유니코드는 Perl의 확장을 모르고 신경도 쓰지 않아요. 현재 Perl 확장을 의미하는 이름이, 더 새로운 유니코드 릴리스를 쓰는 미래의 perl 인터프리터 버전에서 경고 없이 다른 유니코드 속성을 의미하도록 바뀔 수 있고, 그러면 여러분의 코드가 더 이상 작동하지 않을 수 있어요. 완전성을 위해 여기서 확장을 언급할게요: 블록 이름을 가져와 그중 하나를 접두사로 붙이세요: In(예를 들어 \p{Blk=Arrows}는 현재 \p{In_Arrows}로 쓸 수 있어요), 때로는 Is(\p{Is_Arrows}처럼), 때로는 아무 접두사도 없이(\p{Arrows}). 이 글을 쓰는 시점(유니코드 9.0)에 In_ 접두사 사용과의 충돌은 없지만, 다른 두 형태와는 충돌이 많아요. 예를 들어 \p{Is_Hebrew}\p{Hebrew}\p{Script_Extensions=Hebrew}를 뜻하는데, 이는 \p{Blk=Hebrew}와 같은 것이 아니에요. 우리의 과거 조언은 블록을 단일 형태로 지정할 때 In_ 접두사를 쓰라는 것이었어요. 하지만 유니코드 8.0이 In으로 시작하는 이름의 속성들을 추가했고, 지금까지 충돌을 막아온 것은 순전히 운이라는 게 분명해졌어요. In을 쓰는 것은 Blk:보다 타이핑이 아주 조금 덜할 뿐이고, 후자의 의미가 어쨌든 더 명확하며 충돌하지 않음이 보장돼요. 그러니 모험하지 마세요. 새 코드에는 \p{Blk=foo}를 쓰세요. 그리고 그 블록이 정말 정말 원하는 것인지 확인하세요. 대부분의 경우 원하는 것은 문자 체계일 거예요.

블록의 완전한 목록은 perluniprops에 있어요.

기타 속성 (Other Properties)

여기서 설명한 아주 기본적인 것들보다 훨씬 많은 속성이 있어요. 완전한 목록은 perluniprops에 있어요.

유니코드는 모든 속성을 복합 형태로 정의하므로, 모든 단일 형태 속성은 Perl 확장이에요. 대부분은 유니코드 속성의 동의어일 뿐이지만, 일부는 진짜 확장이며 그중에는 복합 형태인 것도 몇 개 있어요. 그리고 꽤 많은 것들이 실제로 유니코드(https://www.unicode.org/reports/tr18)가 권장하는 것이에요.

이 절은 복합 형태 유니코드 속성의 동의어가 아닌 모든 확장에 대한 세부사항을 줘요(그런 속성들에 대해서는 유니코드 표준을 참조해야 해요: https://www.unicode.org/reports/tr44).

\p{All}

이것은 가능한 모든 코드 포인트와 일치해요. qr/./s와 동일해요. 다른 모든 비-사용자-정의 \p{} 속성 매칭과 달리, 이 속성을 비-유니코드 코드 포인트에 대해 일치시키면 경고가 절대 생성되지 않아요('유니코드 코드 포인트를 넘어서' 참고).

\p{Alnum}

이것은 \p{Alphabetic} 또는 \p{Decimal_Number} 문자와 일치해요.

\p{Any}

이것은 1_114_112개의 유니코드 코드 포인트 중 아무것과 일치해요. \p{Unicode}의 동의어예요.

\p{ASCII}

이것은 유니코드의 부분 집합인 US-ASCII 문자 집합의 128개 문자 중 아무것과 일치해요.

\p{Assigned}

이것은 할당된 코드 포인트, 즉 일반 범주가 Unassigned(또는 동등하게 Cn)가 아닌 코드 포인트와 일치해요.

\p{Blank}

이것은 \h\p{HorizSpace}와 같아요: 공간을 가로로 바꾸는 문자예요.

\p{Decomposition_Type: Non_Canonical} (짧게 \p{Dt=NonCanon})

비-정규 분해 타입을 가진 문자와 일치해요. 정규 분해는 위의 '확장 자소 클러스터 (논리 문자)' 절에서 소개됐어요. 하지만 훨씬 더 많은 문자가 다른 타입의 분해를 가지는데, 일반적으로 '호환(compatible)' 분해 또는 '비-정규(non-canonical)' 분해라고 불러요. 이런 분해를 이루는 시퀀스는 선결합 문자와 정규 동등한 것으로 간주되지 않아요. 예로 "SUPERSCRIPT ONE"이 있어요. 보통 숫자 1과 비슷하지만 정확히 같지는 않고, 숫자 1로의 분해는 '호환' 분해, 특히 'super'('위첨자') 분해라고 불러요. 그런 호환 분해가 여러 개 있어요(https://www.unicode.org/reports/tr44 참고). \p{Dt: Non_Canon}은 이 모든 것의 합집합을 하나의 이름으로 가리키는 Perl 확장이에요.

대부분의 유니코드 문자는 분해가 없어서 분해 타입이 "None"이에요. 따라서 Non_Canonical은 다음과 동일해요:

qr/(?[ \P{DT=Canonical} - \p{DT=None} ])/

(비-정규 분해 중 하나가 "compat"라고 이름 붙었는데, 어쩌면 "miscellaneous"라고 더 잘 부를 수 있었을 거예요. 유니코드가 더 나은 일반 이름을 찾지 못한 것들만 포함해요.)

\p{Graph}

그래픽(graphic)인 모든 문자와 일치해요. 이론적으로는 프린터에서 잉크가 사용되게 하는 문자를 의미해요.

\p{HorizSpace}

이것은 \h\p{Blank}와 같아요: 공간을 가로로 바꾸는 문자예요.

\p{In=*}

이것은 \p{Present_In=*}의 동의어예요.

\p{PerlSpace}

ASCII로 제한된 \s와 같아요. 즉 [ \f\n\r\t]이고, Perl v5.18부터는 세로 탭도 포함해요. 기억법: Perl의 (원래) 공백.

\p{PerlWord}

ASCII로 제한된 \w와 같아요. 즉 [A-Za-z0-9_]. 기억법: Perl의 (원래) 단어.

\p{Posix...}

이런 것이 여러 개 있는데, Posix 클래스에 대한 \p{} 표기법을 사용한 동등물이며 perlrecharclass의 "POSIX Character Classes"에 설명돼 있어요.

\p{Present_In: *} (짧게 \p{In=*})

이 속성은 문자가 어떤 유니코드 버전에 있는지 알아야 할 때 사용해요.

위의 "*"는 1.1이나 12.0 같은 어떤 유니코드 버전 번호를 뜻하며, "Unassigned"를 뜻할 수도 있어요. 이 속성은 버전 번호가 주는 유니코드 릴리스 기준으로 최종 상태가 정해진 코드 포인트와 일치해요. \p{Present_In: Unassigned}은 아직 의미가 할당되지 않은 코드 포인트와 일치해요.

예를 들어 U+0041 "LATIN CAPITAL LETTER A"는 사용 가능한 첫 유니코드 릴리스인 1.1에 이미 있었으므로, 모든 유효한 "" 버전에 대해 이 속성이 참이에요. 반면 U+1EFF는 5.1 버전에서 "LATIN SMALL LETTER Y WITH LOOP"가 되면서 할당됐으므로, 그것과 일치하는 ""는 5.1, 5.2 그리고 그 이후뿐이에요.

유니코드는 여기에서 파생된 Age 속성을 제공해요. Age의 문제는 (Perl이 취하는) 엄격한 해석에서는 그것이 코드 포인트의 의미가 도입된 정확한 릴리스와 일치한다는 거예요. 그래서 U+0041은 1.1에만 일치하고, U+1EFF는 5.1에만 일치해요. 이것은 보통 원하는 것이 아니에요.

일부 비-Perl 구현은 Age 속성의 의미를 Perl의 Present_In 속성과 같게 바꿀 수 있어요. 그 가능성에 유의하세요.

이 두 속성 모두에 관한 또 다른 혼란은, 정의가 코드 포인트가 할당됐다는 것이 아니라 코드 포인트의 의미가 결정됐다는 거예요. 이것은 66개의 코드 포인트가 항상 미할당 상태로 남을 것이기 때문이에요. 그래서 그들의 Age는 그러게 만들기로 결정한 유니코드 버전이에요. 예를 들어 U+FDD0는 문자로 영구히 미할당된 채 남을 것이고, 그렇게 하기로 한 결정은 3.1 버전에서 이뤄졌어요. 그래서 \p{Age=3.1}은 이 문자와 일치하고, \p{Present_In: 3.1}과 그 이상도 마찬가지예요.

\p{Print}

그래픽이거나 공백인 문자와 일치하되, 제어 문자는 제외해요.

\p{SpacePerl}

ASCII 너머를 포함한 \s와 같아요. 기억법: Perl이 수정한 공백. (v5.18까지는 세로 탭을 포함하지 않는데, Posix 표준과 유니코드 모두 이것을 공백으로 간주해요.)

\p{Title}\p{Titlecase}

대소문자 구분 매칭 아래에서는 둘 다 \p{General Category=Titlecase_Letter}(\p{gc=lt})와 같은 코드 포인트와 일치해요. 차이는 /i 대소문자 무시 매칭 아래에서는 이들이 \p{Cased}와 같이 일치하는 반면, \p{gc=lt}\p{Cased_Letter}와 일치한다는 거예요.

\p{Unicode}

이것은 1_114_112개의 유니코드 코드 포인트 중 아무것과 일치해요. \p{Any}와 동일해요.

\p{VertSpace}

이것은 \v와 같아요: 공간을 세로로 바꾸는 문자예요.

\p{Word}

ASCII 너머의 10만 개가 넘는 문자를 포함한 \w와 같아요.

\p{XPosix...}

이런 것이 여러 개 있는데, 표준 Posix 클래스를 완전한 유니코드 범위까지 확장한 것이에요. perlrecharclass의 "POSIX Character Classes"에 설명돼 있어요.

\N{...}\p{name=...}의 비교 (Comparison of \N{...} and \p{name=...})

Perl 5.32부터 정규식 패턴에서 \p{name=...}을 사용해 이름으로 문자를 지정할 수 있어요. 이는 오래된 \N{...} 사용 방식에 더해진 것이에요. 이 둘 사이의 차이를 요약하면:

                          \N{...}       \p{Name=...}
보간(interpolate) 가능     eval에서만     네          [1]
사용자 정의 이름            네            아니오       [2]
이름 앨리어스               네            네          [3]
이름 있는 시퀀스            네            네          [4]
이름 값 파싱               정확          유니코드 느슨 [5]

[1] 보간 능력은 다음처럼 할 수 있다는 뜻이에요:

qr/\p{na=latin capital letter $which}/

그리고 $which를 다른 곳에서 지정하면 돼요.

[2] \N{...}을 쓸 때는 자신만의 문자 이름을 만들고 공식 이름을 오버라이드할 수 있어요. charnames의 "CUSTOM ALIASES"를 참고하세요.

[3] 일부 문자는 여러 이름(동의어)을 가져요.

[4] 특정 문자 시퀀스는 개별 이름 외에 단일 이름이 주어져요.

[5] 정확한 이름 값 매칭은 원하는 이름에서 대소문자, 하이픈, 밑줄, 공백을 정확히 지정해야 한다는 뜻이에요. 느슨한 매칭은 유니코드 규칙(https://www.unicode.org/reports/tr44/tr44-24.html#UAX44-LM2)을 따르는데, 여기서는 이것들이 대부분 무관해요. 몇 가지 특이한 문자 이름을 빼면, 이것들은 다른 어떤 \p{...} 속성에서 이미 쓰이는 규칙과 같아요.

속성 값의 와일드카드 (Wildcards in Property Values)

Perl 5.30부터 이런 것을 할 수 있어요:

qr!\p{numeric_value=/\A[0-5]\z/}!

또는 축약하고 /x를 추가해서:

qr! \p{nv= /(?x) \A [0-5] \z / }!

이것은 숫자 값이 0, 1, 2, 3, 4, 5 중 하나인 모든 코드 포인트와 일치해요. 이 특정 예제는 이전 perl에서 이렇게 쓸 수도 있었어요:

qr! \A [ \p{nv=0}\p{nv=1}\p{nv=2}\p{nv=3}\p{nv=4}\p{nv=5} ] \z !xx

그래서 이 경우 이 기능은 그저 더 쉽고 짧게 쓰게 해 줄 뿐이에요. \A\z를 넣지 않았다면, 이것들은 1/2 같은 것과 일치했을 거예요. 왜냐하면 그것이 1(그리고 2)을 포함하니까요. 쓴 대로는, 그런 숫자 값을 가진 아래첨자 같은 것과 일치해요. 그 숫자 값을 가진 10진 숫자만 원한다면 이렇게 말할 수 있어요:

qr! (?[ \d & \p{nv=/[0-5]/ ]) }!x

\d가 패턴을 앵커할 필요를 없애 주는데, 결과가 [0-9]만 일치하도록 강제하고 [0-5]가 더 제한해 주기 때문이에요.

위 예제들에서 "/" 문자 사이에 둘러싸인 텍스트는 거의 아무 정규식이든 될 수 있어요. 주 패턴과 독립적이라서 캡처링 그룹 등을 공유하지 않아요. 그것의 구분자는 ASCII 구두점이어야 하지만, "{""}"로 구분되면 안 되고 리터럴 "}"를 포함해도 안 돼요. }가 둘러싸는 \p{}의 끝을 구분하니까요. 어떤 패턴이든 특정 다른 구분자는 그 거울 이미지로 종결돼요. 그것들은 "(", "[", "<"이에요. 구분자가 "-", "_", "+", "\" 중 하나이거나 둘러싸는 패턴에 쓰인 것과 같은 구분자라면, 앞뒤 모두 백슬래시 이스케이프가 앞에 와야 해요.

"$"를 사용해 문자열의 끝 일치를 표시하는 것에 주의하세요. 그것이 $/ 같은 구두점 변수로 너무 쉽게 해석될 수 있어요.

마지막 구분자 뒤에는 어떤 수정자도 올 수 없어요. 대신 "(?adlupimnsx-imnsx)" 및/또는 "(?adluimnsx-imnsx:pattern)"을 사용해 수정자를 지정하세요. 하지만 특정 수정자는 와일드카드 하위 패턴에서 불법이에요. 지정할 수 있는 유일한 문자 집합 수정자는 /aa예요. 다른 어떤 문자 집합, 그리고 -m, p, s는 모두 불법이에요. (?...) 표기법에서 불법인 qr/.../gc 같은 수정자를 지정하면 보통 경고가 나지만, 와일드카드 하위 패턴에서는 그 사용이 오류예요. m 수정자는 효과가 없어요. 일치하는 모든 것은 단일 라인이 될 거예요.

기본적으로 패턴은 /i가 지정된 것처럼 대소문자를 무시하고 일치해요. 패턴에서 (?-i)라고 말해 이것을 바꿀 수 있어요.

또한 불법적인 특정 연산이 있어요. 와일드카드 하위 패턴 안에서 \p{...}\P{...} 호출을 중첩할 수 없고, \G는 말이 안 되므로 역시 금지돼요.

그리고 * 수량자(또는 그 동등물 (0,})는 불법이에요.

이 기능은 왼쪽이 Is_로 접두되면 사용할 수 없고, perluniprops의 "Discouraged"에서 "Discouraged"로 표시된 어떤 형태에서도 사용할 수 없어요.

이 실험적 기능은 https://www.unicode.org/reports/tr18/#Wildcard_Properties를 구현하기 시작하려고 추가된 것이에요. 사용하면 experimental::uniprop_wildcards 범주에서 (기본-켜짐) 경고가 나요. 우리는 경험을 쌓으면서 그 동작을 바꿀 권리를 보유해요.

하위 패턴은 거의 뭐든 될 수 있지만, 유용하려면 다음 중 하나 또는 둘 다에 대해 호출될 때 일치해야 해요: a) 밑줄(그리고 Block 속성에서는 공백)과 일부 대문자를 가진 속성 값의 전체 이름, 또는 b) 공백과 밑줄을 압축한 전부 소문자의 속성 값. 예를 들어:

qr!\p{Blk=/Old I.*/}!
qr!\p{Blk=/oldi.*/}!

는 같은 것과 일치해요.

\p{...} 안에서 /x가 없어도 공백을 가질 수 있음을 보여주는 또 다른 예:

qr!\p{scx= /Hebrew|Greek/ }!

안전을 위해 위 예제를 앵커했어야 했을 거예요. Hebrew_Braille 같은 것과의 일치를 막기 위해서요. 하지만 지금까지 그런 문자 체계 이름은 없어요. 속성의 합법적 값 중 어느 것도 패턴과 일치하지 않으면 경고가 발행돼요. 아마 미래 릴리스에서는 패턴이 모든 가능한 코드 포인트를 일치시키게 되면 경고를 올릴 거예요.

5.32부터 Name, Name Aliases, Named Sequences 속성을 일치시키는 것이 허용돼요. 이것들은 오래 전부터 \N{}에서 그래 왔듯이 단일 조합 속성으로 간주돼요. 이들에 대해 느슨한 매칭은 다른 속성의 값과 정확히 같은 방식으로 작동하지 않아요. 규칙은 https://www.unicode.org/reports/tr44/tr44-24.html#UAX44-LM2에 나와 있어요. 결과적으로 Perl은 다른 속성에서처럼 여러분을 위해 느슨한 매칭을 시도하지 않아요. 이름의 모든 글자는 대문자지만, 하위 패턴에 (?i)를 추가해 대소문자를 무시할 수 있어요. 공백이 어디인지 확실하지 않으면 하위 패턴에서 ?를 사용할 수 있어요. 어떤 문자 이름도 밑줄을 포함하지 않으니 일치시키려 애쓰지 마세요. 하이픈 사용은 특히 문제가 많은데, 위 링크를 참조하세요. 하지만 유니코드 13.0 기준으로, 현대 용법에서 이것에 이상함이 있는 유일한 문자 체계는 티베트어이고, 또한 두 한국어 문자 U+116C HANGUL JUNGSEONG OE와 U+1180 HANGUL JUNGSEONG O-E가 있어요. 유니코드는 미래에 하이픈 문제 이름을 추가하지 않겠다고 약속하지 않아요.

이것들에 와일드카드를 사용하면, 확인해야 할 수십만 개의 합법적 이름이 있으므로 자원을 많이 소모해요.

Name 속성 와일드카드 사용 예는:

qr!\p{name=/(SMILING|GRINNING) FACE/}!

또 다른 하나:

qr/(?[ \p{name=\/CJK\/} - \p{ideographic} ])/

이것은 (유니코드 13.0 기준) 이데오그래프가 아닌 200여 개의 CJK 문자예요.

와일드카드 하위 패턴이 현재 작동하지 않는 특정 속성들이 있어요. 이것들은:

Bidi Mirroring Glyph
Bidi Paired Bracket
Case Folding
Decomposition Mapping
Equivalent Unified Ideograph
Lowercase Mapping
NFKC Case Fold
Titlecase Mapping
Uppercase Mapping

@unicode_property@ 형식도 구현되지 않았어요.

어떤 (단일) 문자 체계에서든 IPV4 인터넷 프로토콜 주소를 일치시키는 완전한 예제가 있어요:

no warnings 'experimental::uniprop_wildcards';

# 하위 문자열을 일치시킬 수 있으므로 이 중간 정규식은 최종 사용에서
# 맥락이나 앵커링이 필요합니다. nt=de를 쓰면 10진 숫자가 나옵니다.
# 이들의 하위 집합을 지정할 때는 U+00B2 SUPERSCRIPT TWO 같은 것이
# 일치하지 않도록 \d를 포함해야 합니다.
my $zero_through_255 =
 qr/ \b (*sr:                                  # 모두 같은 문자 체계에서
           (?[ \p{nv=0} & \d ])*               # 선택적 앞쪽 0
       (                                       # 그중 하나:
                                 \d{1,2}       #   0 - 99
           | (?[ \p{nv=1} & \d ])  \d{2}       #   100 - 199
           | (?[ \p{nv=2} & \d ])
              (  (?[ \p{nv=:[0-4]:} & \d ]) \d #   200 - 249
               | (?[ \p{nv=5}     & \d ])
                 (?[ \p{nv=:[0-5]:} & \d ])    #   250 - 255
              )
       )
     )
   \b
 /x;

my $ipv4 = qr/ \A (*sr:         $zero_through_255
                        (?: [.] $zero_through_255 ) {3}
                  )
               \z
           /x;

사용자 정의 문자 속성 (User-Defined Character Properties)

"In"이나 "Is"로 시작하는 이름의 서브루틴을 정의해 자신만의 2진 문자 속성을 정의할 수 있어요. (정규식 집합 기능 "(?[ ])"은 더 복잡한 정의를 허용하는 대안을 제공해요.) 서브루틴은 어떤 패키지에서도 정의할 수 있어요. 모든 공식 유니코드 속성처럼 그 이름은 ASCII만이어야 하고, 같은 이름을 쓰는 유니코드 속성을 오버라이드해요. 사용자 정의 속성은 정규식 \p{}\P{} 구성에서 쓸 수 있어요. 자신이 있는 패키지가 아닌 다른 패키지의 사용자 정의 속성을 쓰려면 \p{}이나 \P{} 구성 안에서 그 패키지를 지정해야 해요.

# Lang::에 IsForeign 속성이 정의되어 있다고 가정
package main;  # 속성 패키지 이름 필요
if ($txt =~ /\p{Lang::IsForeign}+/) { ... }

package Lang;  # 속성 패키지 이름 불필요
if ($txt =~ /\p{IsForeign}+/) { ... }

서브루틴은 단일 파라미터를 전달받는데, 대소문자 구분 매칭이 적용 중이면 0이고 대소문자 무시 매칭이 적용 중이면 0이 아니에요. 서브루틴은 플래그 값에 따라 다른 값을 반환할 수 있어요. 하지만 서브루틴은 각 플래그 값(0 vs 비-0)에 대해 두 번 이상 호출되지 않아요. 반환 값은 저장되어 다시 서브를 호출하는 대신 사용돼요. 패턴을 컴파일하는 시점에 서브가 정의돼 있으면 그때 호출되고, 아니면 실행 중에 (그 플래그에 대한) 값이 필요해지는 처음에 호출돼요.

정규식이 taint됐다면, 서브루틴의 이름이 taint된 데이터로 결정될 때 Perl은 서브루틴을 호출하는 대신 die한다는 점을 주목하세요.

서브루틴은 특수한 형식의 문자열을 반환해야 하며, 하나 이상의 개행으로 구분된 줄을 가져요. 각 줄은 다음 중 하나여야 해요:

  • 유니코드 속성 타입,
  • 범위(\t로 구분된 두 코드 포인트) 등.

예를 들어 일본어의 두 음절 문자 체계(히라가나와 가타카나)를 모두 다루는 속성을 정의하려면:

sub InKana {
    return <<END;
3040\t309F
30A0\t30FF
END
}

here-doc 끝 마커가 줄의 시작에 있다고 상상하세요. 이제 \p{InKana}\P{InKana}를 쓸 수 있어요.

기존 블록 속성 이름을 쓸 수도 있었어요:

sub InKana {
    return <<'END';
+utf8::InHiragana
+utf8::InKatakana
END
}

할당된 문자만 일치시키고 원시 블록 범위를 일치시키고 싶지 않다고 가정해 봐요. 즉 미할당 문자를 제거하고 싶은 거예요:

sub InKana {
    return <<'END';
+utf8::InHiragana
+utf8::InKatakana
-utf8::IsCn
END
}

부정은 (놀랍게도) 부정된 클래스를 정의하는 데 유용해요.

sub InNotKana {
    return <<'END';
!utf8::InHiragana
-utf8::InKatakana
+utf8::IsCn
END
}

이것은 모든 비-유니코드 코드 포인트와 일치해요. 그들 각각이 Kana에 없으니까요. 교집합을 사용해 이것들을 제외할 수 있어요. 이 수정된 예제가 보여주듯이:

sub InNotKana {
    return <<'END';
!utf8::InHiragana
-utf8::InKatakana
+utf8::IsCn
&utf8::Any
END
}

&utf8::Any는 정의에서 마지막 줄이어야 해요.

교집합은 일반적으로 둘(또는 그 이상)의 클래스가 일치시키는 공통 문자를 얻는 데 쓰여요. 첫 번째 집합에 "&"를 쓰지 않는 것을 기억하는 게 중요해요. 그것은 아무것도 없는 것과 교집합하는 것이 되어 빈 집합이 되니까요. (마찬가지로 첫 번째 집합에 "-"를 쓰는 것도 아무것도 하지 않아요.)

비-사용자-정의 \p{} 속성 매칭과 달리, 이 속성들을 비-유니코드 코드 포인트에 대해 일치시키면 경고가 절대 생성되지 않아요('유니코드 코드 포인트를 넘어서' 참고).

사용자 정의 대소문자 매핑 (User-Defined Case Mappings, 심각한 해커 전용)

이 기능은 Perl 5.16에서 제거됐어요. CPAN 모듈 Unicode::Casing은 이 기능이 가졌던 단점 없이 더 나은 기능을 제공해요. 5.16보다 이른 Perl을 쓴다면, 이 기능은 이 pod의 5.14 버전에 가장 완전히 문서화돼 있어요: http://perldoc.perl.org/5.14.0/perlunicode.html#User-Defined-Case-Mappings-%28for-serious-hackers-only%29

입력·출력용 문자 인코딩 (Character Encodings for Input and Output)

Encode를 참고하세요.

정규식 유니코드 지원 수준 (Unicode Regular Expression Support Level)

다음은 정규식에 대한 유니코드 지원 기능 목록으로, 코어 Perl이 현재 직접 지원하는 모든 기능을 설명해요. "Level N" 참조와 절 번호는 UTS#18 "Unicode Regular Expressions", 2016년 10월 18번째 버전(https://www.unicode.org/reports/tr18)을 가리켜요.

레벨 1 - 기본 유니코드 지원 (Level 1 - Basic Unicode Support)

RL1.1   Hex Notation (16진 표기)              - Done          [1]
RL1.2   Properties (속성)                     - Done          [2]
RL1.2a  Compatibility Properties              - Done          [3]
RL1.3   Subtraction and Intersection          - Done          [4]
RL1.4   Simple Word Boundaries                - Done          [5]
RL1.5   Simple Loose Matches                  - Done          [6]
RL1.6   Line Boundaries (줄 경계)              - Partial       [7]
RL1.7   Supplementary Code Points             - Done          [8]

[1] \N{U+...}\x{...}.

[2] \p{...} \P{...}. 이 요구사항은 최소 속성 목록을 위한 것이에요. Perl은 이것들을 지원해요. 다른 속성은 R2.7을 참고하세요.

[3] Perl은 \d \D \s \S \w \W \X [:prop:] [:^prop:]과, https://www.unicode.org/reports/tr18/#Compatibility_Properties가 지정하는 모든 속성을 가져요. 이것들은 위의 '기타 속성'에서 설명됐어요.

[4] v5.18부터 시작하는 정규식 집합 기능 "(?[...])"이 이것을 달성해요. perlre의 "(?[ ])" 참고.

[5] \b \B은 이 요구사항의 세부사항 대부분을 충족하지만 전부는 아니고, \b{wb}\B{wb}은 충족하며 더 엄격한 R2.3도 충족해요.

[6] Perl은 매칭에서 Simple이 아니라 Full case-folding을 수행한다는 점을 주목하세요.

예를 들어 U+1F88은 그냥 U+1F80이 아니라 U+1F00 U+03B9와 동일해요. 이 차이는 특정 수식자를 가진 특정 그리스 대문자에서 주로 중요해요. Full case-folding은 문자를 분해하는 반면, Simple case-folding은 그것을 단일 문자에 매핑할 거예요.

[7] 이것이 부분적으로만 구현된 것으로 간주되는 이유는 Perl에 UAX#14 "Unicode Line Breaking Algorithm"에 부합하는 qr/\b{lb}/Unicode::LineBreak가 있기 때문이에요. 정규식 구성은 기본 동작을 제공하고, 더 무거운 모듈은 커스터마이징 가능한 줄 바꿈을 제공해요.

하지만 Perl은 \n을 줄 시작·끝 구분자로 취급하는 반면, 유니코드는 그렇게 해석돼야 하는 문자가 더 있다고 지정해요. 이것들은:

VT   U+000B  (\v in C)
FF   U+000C  (\f)
CR   U+000D  (\r)
NEL  U+0085
LS   U+2028
PS   U+2029

정규식 패턴의 ^$는 이 모든 것과 일치해야 하는데 일치하지 않아요. 이 문자들은 <>, $., 스크립트 줄 번호에도 영향을 줘야 하지만 그러지 않아요.

또한 줄은 CRLF 안에서 분할되어서는 안 돼요(즉 \r\n 사이에 빈 줄이 없어야 해요). CRLF에는 :crlf 레이어를 시도해 보세요(PerlIO 참고).

[8] Perl에서 쓰는 UTF-8/UTF-EBCDIC은 U+10000에서 U+10FFFF뿐 아니라 U+10FFFF 너머도 허용해요.

레벨 2 - 확장 유니코드 지원 (Level 2 - Extended Unicode Support)

RL2.1   Canonical Equivalents          - 유니코드가 철회  [9]
RL2.2   Extended Grapheme Clusters     - Partial       [10]
        and Character Classes with Strings
RL2.3   Default Word Boundaries        - Done          [11]
RL2.4   Default Case Conversion        - Done
RL2.5   Name Properties                - Done
RL2.6   Wildcards in Property Values   - Partial       [12]
RL2.7   Full Properties                - Partial       [13]
RL2.8   Optional Properties            - Partial       [14]

[9] 유니코드는 UTS#18의 이 부분을, 정규 동등성(UAX#15 "Unicode Normalization Forms" 참고)을 얻는 것은 기본적으로 프로그래머 수준에서 하는 것이라고 하도록 다시 썼어요. 정규식과 그것에 일치시킬 텍스트를 둘 다 쓰려면 NFD를 사용하세요(Unicode::Normalize를 쓸 수 있어요).

[10] Perl은 \X\b{gcb}를 가져요. 유니코드는 "Grapheme Cluster Mode"를 철회했고, 최근에 문자열 속성을 추가했는데 Perl은 아직 지원하지 않아요.

[11] UAX#29 "Unicode Text Segmentation" 참고: https://www.unicode.org/reports/tr29.

[12] 위의 '속성 값의 와일드카드' 참고.

[13] Perl은 유니코드 문자 데이터베이스(UCD)의 모든 속성을 지원해요. 다른 유니코드 소스에서 오는 나열된 속성은 아직 지원하지 않아요.

[14] Perl이 지원하는 유일한 선택 속성은 Named Sequence예요. 이 속성 중 어느 것도 UCD에 없어요.

레벨 3 - 맞춤 지원 (Level 3 - Tailored Support)

이것은 유니코드가 철회했어요.

유니코드 인코딩 (Unicode Encodings)

유니코드 문자는 코드 포인트에 할당되는데, 코드 포인트는 추상적인 숫자예요. 이런 숫자를 사용하려면 다양한 인코딩이 필요해요.

최소한 다음 인코딩이 있습니다:

  • UTF-8 (및 EBCDIC 플랫폼에서는 UTF-EBCDIC).

자세한 내용은 perlunitut, perluniintro, utf8을 참고하세요.

비문자 코드 포인트 (Noncharacter code points)

유니코드는 66개의 코드 포인트를 '비문자 코드 포인트(noncharacter code point)'로 따로 떼어 놓아요. 이것들은 모두 Unassigned(Cn) "General_Category"를 가지며, 어느 것에도 문자가 결코 할당되지 않을 거예요. 그것들은 U+FDD0U+FDEF 사이(포함)의 32개 코드 포인트와 다음 34개 코드 포인트예요:

U+FFFE   U+FFFF
U+1FFFE  U+1FFFF
U+2FFFE  U+2FFFF
...
U+EFFFE  U+EFFFF
U+FFFFE  U+FFFFF
U+10FFFE U+10FFFF

유니코드 7.0까지는 비문자가 "유니코드 텍스트 데이터의 공개 교환(open interchange)에서 사용 금지"였어요. 그래서 그 스트림을 처리하는 코드는 이 코드 포인트를 문자 데이터와 섞일 수 있고 항상 그 데이터와 구별할 수 있는 센티널(sentinel)로 쓸 수 있었어요. (위와 다음 문단의 강조는 이 문서에서 추가된 것이에요.)

유니코드 7.0은 표현을 바꿔서 "유니코드 텍스트 데이터의 공개 교환에서 사용을 권장하지 않음"으로 만들었어요. 7.0 표준은 이어서 말해요:

이 변경은 에디터나 소스 코드 제어 같은 다양한 상용 도구가, 이 코드 포인트를 쓰는 프로그램 파일을 처리하지 않도록 작성된 것으로 밝혀졌기 때문에 이루어졌어요. 그 효과는 그 사용을 거의 완전히 배제하는 것이었죠! 그리고 그것은 결코 의도가 아니었어요. 그것들은 항상 애플리케이션 안이나 협력하는 애플리케이션 집합 안에서 마음대로 사용 가능하도록 의도됐어요.

에디터처럼 어떤 유니코드 텍스트 데이터든 처리할 수 있어야 하는 코드를 작성한다면, 여러분 자신이 이 코드 포인트를 쓰지 말고 대신 입력에서 허용해야 해요. 센티널이 필요하면, 그것들은 합법적 유니코드가 아닌 것이어야 해요. UTF-8 데이터의 경우 바이트 0xC0과 0xC1을 센티널로 쓸 수 있는데, 그것들은 잘 형성된(well-formed) UTF-8에 결코 나타나지 않으니까요. (UTF-EBCDIC에도 동등한 것이 있어요.) 또한 유니코드 코드 포인트를 정수 변수에 저장하고 음수 값을 센티널로 쓸 수도 있어요.

그런 도구를 작성하는 게 아니라면, 입력으로 비문자를 받아들일지 여부는 여러분에게 달려 있어요(표준은 받아들이지 말 것을 권장하지만요). Perl로 엄격한 입력 스트림 검사를 한다면, 이 코드 포인트들은 계속 금지돼요. 이것은 하위 호환성을 유지하기 위한 거예요(그렇지 않으면 잠재적 보안 구멍이 열릴 수 있어요. 비문자가 자신에게 도달하기 전에 걸러질 것이라고 가정하고 작성된, 의심하지 않는 애플리케이션이 이제 경고 없이 그것들을 받기 시작할 수 있으니까요). 엄격한 검사를 하려면 :encoding('UTF-8') 레이어를 쓸 수 있어요.

Perl은 비문자를 출력하려는 시도가 있으면 계속 경고(utf8의 하위 범주인 "nonchar" 경고 범주 사용)해요.

유니코드 코드 포인트를 넘어서 (Beyond Unicode code points)

최대 유니코드 코드 포인트는 U+10FFFF이고, 유니코드는 그까지의 코드 포인트에 대해서만 연산을 정의해요. 하지만 Perl은 플랫폼에서 사용 가능한 최대 허용 부호 있는 숫자까지의 코드 포인트에 대해 작동해요. 하지만 Perl은 느슨한 규칙이 사용되고 있지 않으면 이것들을 입력 스트림에서 받아들이지 않고, 출력되면(utf8의 하위 범주인 "non_unicode" 경고 범주 사용) 경고해요.

이 코드 포인트들에 대해 유니코드 규칙이 정의돼 있지 않기 때문에, 그 위에 유니코드 정의 연산이 수행되면 Perl은 우리가 합리적이라고 믿는 규칙을 사용하면서 일반적으로 "non_unicode" 범주로 경고해요. 예를 들어 uc("\x{11_0000}")은 그런 경고를 생성하고, 입력 파라미터를 결과로 반환해요. Perl이 모든 비-유니코드 코드 포인트의 대문자를 코드 포인트 자신으로 정의하기 때문이에요. (대문자화뿐 아니라 모든 대소문자 변경 연산이 이렇게 작동해요.)

정규식에서 유니코드 속성 \p{} \P{} 구성을 이 코드 포인트들에 대해 일치시키는 상황은 그렇게 명확하지 않고, 경험을 쌓으면서 그 처리 방식이 바뀌었어요.

한 가지 가능성은 이 코드 포인트에 대한 어떤 일치도 정의되지 않은 것으로 취급하는 거예요. 하지만 Perl에는 일치가 정의되지 않은 상태라는 개념이 없어서, 그것을 실패 또는 FALSE로 변환해요. 이것은 Perl이 v5.14(이 코드 포인트의 사용이 일반적으로 신뢰할 수 있게 된 때)부터 v5.18까지 한 것과 거의, 하지만 완전히는 아닌, 같아요. 차이는 Perl이 모든 \p{} 일치를 실패로, 모든 \P{} 일치는 성공으로 취급했다는 거예요.

이것의 한 문제는 일부 경우에 예상치 못하고 혼란스러운 결과를 낳는다는 거예요:

chr(0x110000) =~ \p{ASCII_Hex_Digit=True}      # <= v5.18에서 실패
chr(0x110000) =~ \p{ASCII_Hex_Digit=False}     # <= v5.18에서 실패!

즉, 두 일치를 모두 정의되지 않은 것으로 취급하고 그것을 false로 변환했어요(각각 경고를 올리며). 첫 번째 경우는 예상된 결과지만, 두 번째는 아마 직관에 반할 거예요: "보수(complement)인데 둘 다 false일 수 있나?" 또 다른 문제는 구현이 많은 유니코드 속성 일치를 이미 존재하는 더 단순하고 빠른 연산으로 최적화하는데, 그런 연산은 경고를 올리지 않는다는 거예요. 우리는 대다수 일치에 도움이 되는 그 최적화를, 유니코드 너머 코드 포인트가 일치되는 드문 사건에 대해 경고를 생성하자고 포기하지 않기로 선택했어요.

이런 문제들의 결과로, v5.20부터 Perl은 비-유니코드 코드 포인트를 그냥 전형적인 미할당 유니코드 문자로 취급하고 그에 따라 일치시켜요. (주목: 유니코드는 비전형적인 미할당 코드 포인트가 있어요. 예를 들어 비문자 코드 포인트와, 할당되면 아랍어·히브리어처럼 오른쪽에서 왼쪽으로 쓰이게 될 운명인 것들이 있어요. Perl은 어떤 비-유니코드 코드 포인트도 비전형적인 속성을 갖지 않는다고 가정해요.)

Perl은 대부분의 경우, 유니코드 너머 코드 포인트를 유니코드 속성에 대해 일치시킬 때 \p{}에서 결과가 TRUE이고 \P{}에서 FALSE일 때 경고를 올려요. 예를 들어:

chr(0x110000) =~ \p{ASCII_Hex_Digit=True}      # 실패, 경고 없음
chr(0x110000) =~ \p{ASCII_Hex_Digit=False}     # 성공, 경고 있음

이 두 예제 모두에서 일치되는 문자가 비-유니코드이므로, 유니코드는 어떻게 일치해야 하는지 정의하지 않아요. 그것은 분명히 ASCII 16진 숫자가 아니므로, 첫 번째 예제는 분명히 실패해야 하고, 그래서 경고 없이 실패해요. 하지만 두 번째 예제가 정의되지 않은, 따라서 FALSE인 결과를 가져야 한다고 주장할 수 있으므로, 그것에 대해 경고가 올라와요.

따라서 경고는 이전 Perl보다 훨씬 적은 경우에 올라오고, 결과가 논쟁의 여지가 있을 수 있을 때만 올라와요. Perl이 만든(또는 앞으로 만들 가능성이 있는) 어떤 최적화도 경고를 건너뛰게 하지 않는다는 것이 밝혀져서, 그것은 Perl의 이전 접근의 두 문제를 모두 해결해요. 이 변경에 영향을 받는 가장 흔히 쓰이는 속성은 \p{Unassigned}인데, 이것은 \p{General_Category=Unassigned}의 짧은 형태예요. v5.20부터 모든 비-유니코드 코드 포인트는 Unassigned로 간주돼요. 이전 릴리스에서는 결과가 정의되지 않은 것으로 간주돼서 일치가 실패했어요.

경고가 올라왔어야 할 때 올라오지 않는 유일한 곳은 최적화가 전체 패턴 일치를 시도조차 하지 않게 하는 경우예요. 예를 들어 Perl은 어떤 문자열이 특정 정규식 패턴과 일치하려면 그 문자열이 "foobar" 하위 문자열을 포함해야 한다는 것을 알아낼 수 있어요. 일치를 시도하기 전에 Perl은 그 하위 문자열을 찾아볼 수 있고, 없으면 실제로 시도하지 않고 즉시 일치를 실패시켜요. 그래서 문자열이 유니코드 너머 코드 포인트를 포함해도 경고가 생성되지 않아요.

이 동작은 대부분의 애플리케이션에서 이전 Perl보다 "내가 뜻한 대로 해(Do what I mean)"에 더 가까워요. 하지만 엄격한 유니코드 준수가 필요한 코드에 대해서는 더 적은 문제를 잡아요. 그래서 그런 코드를 수용할 추가 동작 모드가 있어요. 이 모드는 정규식 패턴이 "non_unicode" 경고 클래스가 치명적(fatal)이 된 어휘 스코프 안에서 컴파일될 때 활성화돼요. 예를 들면:

use warnings FATAL => "non_unicode"

(warnings 참고). 이 동작 모드에서 Perl은 비-유니코드 코드 포인트에 대한 모든 일치(논쟁의 여지가 있는 것뿐 아니라)에 대해 경고를 올리고, 경고가 출력되지 않게 할 수 있는 최적화를 건너뛰어요. (여전히 위의 "foobar" 예제처럼 일치가 시도조차 되지 않으면 경고하지 않아요.)

요약하면, Perl은 이제 일반적으로 정규식 일치에서 비-유니코드 코드 포인트를 전형적인 유니코드 미할당 코드 포인트로 취급하고, 결과가 무엇이어야 하는지 논쟁의 여지가 있을 때만 경고를 올려요. 하지만 이 경고가 치명적으로 만들어진 경우에는 건너뛰지 않아요.

이 모든 것에 한 가지 예외가 있어요. \p{All}은 유니코드 속성처럼 보이지만, 유니코드든 아니든 모든 가능한 코드 포인트에 대해 참으로 정의된 Perl 확장이에요. 그래서 비-유니코드 코드 포인트에 대해 일치시킬 때 경고가 절대 생성되지 않아요. (v5.20 이전에는 코드 포인트 0부터 0x10FFFF까지 일치하는 \p{Any}의 정확한 동의어였어요.)

유니코드의 보안 함의 (Security Implications of Unicode)

유니코드의 보안 함의는 꽤 복잡해요. 세계의 모든 문자 체계를 다루려고 하는 것에서 예상할 수 있듯이요. 소개는 Unicode Security Methods(https://www.unicode.org/reports/tr39)에 있어요.

함정의 몇 가지 예가 있어요:

혼동 가능 문자 (Confusables)

유니코드의 많은 문자는 다른 문자와 충분히 비슷해 보여서 서로 쉽게 혼동될 수 있어요. 이것은 심지어 같은 문자 체계 안에서도 사실이에요. 예를 들어 영어에서 숫자 0과 대문자 O가 서로처럼 보일 수 있죠. 하지만 그 문자 체계를 쓰는 사람들은 그것을 주의하라는 걸 알고 있어요.

유니코드에서 한 문자 체계의 숫자는 다른 문자 체계에서 다른 숫자 값을 가진 숫자와 혼동될 수 있어요. 악성 웹사이트가 이것을 사용해 어떤 것의 가격이 실제로 청구되는 금액보다 적어 보이게 만들 수 있어요. (그런 숫자가 섞이지 않도록 하려면 perlre의 "Script Runs"를 쓸 수 있어요.)

이것은 인터넷 주소의 일반적인 문제예요. 도메인 이름을 주는 사람들은 다른 것을 스푸핑(spoof)하는 이름을 주지 않도록 주의해야 해요(perlre의 "Script Runs"의 예 참고).

그리고 컴퓨터 프로그램 식별자 이름은 실제가 아닌 뭔가처럼 보일 수 있어서, 예를 들어 코드 리뷰어를 속일 수 있어요. 개별 식별자에 대한 스크립트 런은 이런 것을 많이 잡을 수 있지만 전부는 아니에요. ASCII 단어 scope의 모든 글자에는 키릴 문자에 똑같이 생긴 것들이 있는데, 그것들이 진짜 단어를 이루진 않아요. 그 키릴 문자를 그 순서로 쓰는 것은 거의 확실히 스푸핑 시도일 거예요.

잘못된 형태의 텍스트 (Malformed text)

웹사이트와 데이터베이스에 실제로 합법적이지 않은 문자열을 전달하는 공격이 성공적으로 수행돼 왔어요. 수신자는 그것을 깨닫지 못하고, 자신이 입력이 의미한다고 생각하는 것에 기반해, 그렇지 않았다면 하지 않았을 행동을 수행해요. 그런 문자열은 "잘못된 형태(malformed)" 또는 "불량 형태(illformed)"라고 불러요.

그런 공격으로 막대한 돈이 손실됐어요. 그것에 속지 않는 것, 즉 잘못된 형태의 텍스트를 감지하고 적절한 조치(또는 무조치)를 취하는 것이 중요해졌어요.

유니코드 REPLACEMENT CHARACTER(U+FFFD)는 이것을 감지하고 다루는 데 결정적이에요. 그것은 다른 무엇의 대체물임을 나타내는 것 외에는 목적이 없어요. 일반적으로 다이아몬드 모양(45도 회전한 직사각형)의 어두운 배경 위에 흰 물음표로 표시돼요.

잘못된 형태의 문자열을 만나면, 그것을 처리하는 코드는 그 자리에 REPLACEMENT CHARACTER를 대체해야 해요. 어떤 부분이 대체되는지에 대한 엄격한 규칙이 이제 있어요. 대체된 부분이 무엇을 의미하려 했는지 추론하려 하면 안 돼요. 그렇게 하는 것은 공격자의 함정에 빠지는 것일 수 있어요.

공격 벡터의 많은 것이 유니코드의 창시자들이나 Perl 같은 구현자들이 원래 상상하지 못했어요. 허용 가능한 문자열에 대한 규칙은 원래 더 느슨했고, 시행착오가 요구하면서 조여졌어요.

안타깝게도 Perl 인터프리터의 유니코드 문자열 작업 방식은 우리도 공격의 가능성에 대해 순진했을 때 개발됐어요. 기존 코드를 깨뜨리는 것에 대한 우려와 결과에 대한 계속된 순진함 때문에, 그것을 바꾸는 것에 저항이 있었고, 그래서 우리의 구현은 유니코드의 요구사항과 권장사항에 뒤쳐져 있어요. 하지만 수년에 걸쳐 문제를 최소화하기 위한 다양한 개선이 추가됐어요.

그러므로 유니코드로 작업할 때는 최신 버전의 Perl 인터프리터를 사용하는 것이 중요해요. perl v5.44에 이르러서야 알려진 공격 벡터에 대해 완전히 단단해졌어요. 그리고 우리가 대응하기 위해 바꿔야 할 새로운 아이디어를 교활한 공격자들이 미래에 무엇을 떠올릴지 누가 알겠어요. (최근에는 유니코드 표준이 준비되지 않은 새로운 것이 알려지진 않았지만요.) 그리고 CPAN 모듈은 인터프리터 자체보다 쉽게 뒤처질 수 있어요.

문자열에서 REPLACEMENT CHARACTER를 찾는 것이 반드시 공격이 있다는 뜻은 아닌 걸 주의하세요. 그것은 어떤 이유로든 완전히 합법적인 입력 문자예요.

그 이유 중 하나는, 유니코드에 대부분의 문자에 대한 동등물이 있지만 전부는 아닌 인코딩이 있을 때예요. 빠진 것에는 그냥 REPLACEMENT CHARACTER를 쓰면 돼요. 텍스트의 대부분이 번역 가능한 한, 결과는 인간 독자에게 알아들을 수 있을 거예요.

이것은 사실 (잘못된 형태 대체 외에) 유니코드 표준(흔히 TUS로 축약)의 첫 버전에서 REPLACEMENT CHARACTER가 만들어진 주요 이유 중 하나였어요. 그때는 그 이후 추가된 많은 문자가 빠져 있었어요(첫 릴리스는 4만 개의 문자였고, 유니코드 16.0은 거의 30만 개지요).

그리고 비트가 빠지는 전송 오류나 디스크 섹터 실패도 잘못된 형태를 일으킬 수 있어요. REPLACEMENT CHARACTER가 대체되고, 오류율이 충분히 낮으면 결과는 인간에게 알아들을 수 있을 수 있어요.

이제 유니코드가 훨씬 더 완전해지고, 유니코드가 모르는 문자를 찾을 확률이 훨씬 낮아져서, REPLACEMENT CHARACTER의 주된 용도는 문자열의 잘못된 형태를 대체하는 것이에요.

순수 Perl로 프로그래밍할 때는 이런 종류의 미묘함을 처리하는 것을 기본 인터프리터와 모듈에 의존하게 돼요. 하지만 프로그램이 외부 세계와 상호작용하는 데 필요한 인코딩을 아는 것은 여러분의 책임이에요. 예를 들어 파일의 흔한 인코딩은 UTF-8이에요. 다음을 사용해 하나를 읽을 수 있어요:

use PerlIO::encoding;
my $path = "path-to-UTF-8-file";
open my $fh, "<:encoding(UTF-8)", $path
                            or die "Couldn't open $path: $!";

이것은 뒤에서 Encode 모듈을 사용해 $path의 내용을 Perl이 이해할 수 있는 것으로 번역해요. Encode는 광범위한 인코딩을 다루는 법을 알아요. 파일에 출력할 때도 이 방식을 사용하세요:

use PerlIO::encoding;
my $out_path = "path-to-UTF-8-file";
open my $fh, ">:encoding(UTF-8)", $out_path
                            or die "Couldn't open $out_path: $!";

(이미 열린 파일의 인코딩을 바꾸려면 perlfunc의 binmode를 쓸 수도 있어요.)

(Perl 프로그램에 전달되는 인자나 환경 변수와 상호작용할 때 인코딩을 지정하는 옵션은 더 적어요. perlrun의 "PERL_UNICODE"을 참고하세요.)

XS로 작성하지 않는 한 이 항목의 끝까지 건너뛰세요.

XS에서의 보안 함의: XS로 작성하고 유니코드 문자열을 조작할 때는 내부에 대해 더 알아야 해요. perlapi의 "Unicode Support"가 그 작업에 사용 가능한 API 요소를 나열해요. UTF-8이 현재 인터프리터가 유니코드 문자열을 내부적으로 저장하는 방식이에요. UTF-8로 인코딩된 문자열을 파싱할 때는 utf8_to_uv 계열의 함수를 사용해야 해요. 이것은 이름에 그냥 to_uv 대신 to_uvchr이 포함된 이전 API 함수들의 함정을 피해요.

불법 코드 포인트 (Illegal code points)

유니코드는 많은 코드 포인트를 불법이거나 피해야 할 것으로 간주해요. Perl은 그것들을 제외하려 할 수 있는 입력 필터를 통과하기만 하면 일반적으로 어쨌든 받아들여요. 이것들은 위에서 논의됐어요('유니코드 인코딩' 아래 UTF-16의 "Surrogates", "비문자 코드 포인트", "유니코드 코드 포인트를 넘어서" 참고).

XS로 작성한다면, perlapi의 utf8_to_uv 함수 계열에는 이것들의 흔한 종류를 제외할 수 있는 것들이 있어요. 특히 strict_utf8_to_uv는 TUS가 정의한 가장 제한적인 집합 외의 모든 것을 제외해요.

정규식 (Regular Expressions)

유니코드에 익숙하지 않다면 정규식 패턴 매칭이 여러분을 놀라게 할 수 있어요. Perl 5.14부터 이것을 제어할 수 있는 여러 패턴 수정자를 사용할 수 있는데, 문자 집합 수정자(character set modifier)라고 불러요. 자세한 내용은 perlre의 "Character set modifiers"에 있어요.

EBCDIC에서의 Perl 유니코드 (Unicode in Perl on EBCDIC)

유니코드는 EBCDIC 플랫폼에서도 지원돼요. perlebcdic 참고.

ASCII 대 EBCDIC 문제를 구체적으로 논의하지 않는 한, 이 문서와 다른 곳에서의 UTF-8 인코딩 참조는 EBCDIC 플랫폼에서는 UTF-EBCDIC을 의미하는 것으로 읽어야 해요. perlebcdic의 "Unicode and UTF" 참고.

UTF-EBCDIC이 UTF-8과 너무 비슷해서, 그 차이는 대부분 여러분에게 숨겨져 있어요. use utf8(use utfebcdic 같은 것이 아니라)가 스크립트가 플랫폼의 "네이티브" 8비트 유니코드 인코딩에 있다고 선언해요. (마찬가지로 ":utf8" 레이어도요.)

로캘 (Locales)

perllocale의 "Unicode and UTF-8" 참고.

유니코드가 일어나지 않을 때 (When Unicode Does Not Happen)

Perl에는 유니코드(어떤 인코딩이든)를 인자로 주거나 결과로 받거나 둘 다 할 수 있는 곳이 여전히 많지만, Perl이 유니코드 입력·출력의 광범위한 방법과 @ARGV 배열(때로는 UTF-8로 해석될 수 있어요) 같은 몇 가지 다른 "진입점"을 가지고 있음에도 불구하고, 그런 일이 일어나지 않아요.

다음은 그런 인터페이스들이에요. 또한 '"유니코드 버그"'를 보세요. 이 모든 인터페이스에 대해 Perl은 (현재 v5.16.0 기준으로) 단순히 인자와 결과 모두 바이트 문자열을 가정하거나, (폐기된) encoding 프라그마가 쓰였다면 UTF-8 문자열을 가정해요.

Perl이 이런 상황에서 유니코드의 역할을 해결하려 시도하지 않는 한 가지 이유는, 대답이 운영체제와 파일 시스템에 크게 의존하기 때문이에요. 예를 들어 파일 이름이 유니코드일 수 있는지, 그리고 정확히 어떤 종류의 인코딩인지는 정확히 이식 가능한 개념이 아니에요. qxsystem도 마찬가지예요: "커맨드-라인 인터페이스"가(그리고 그중 어느 것이) 유니코드를 얼마나 잘 다룰까요?

"유니코드 버그" (The "Unicode Bug")

"유니코드 버그"라는 용어는 Latin-1 Supplement 블록, 즉 128과 255 사이의 코드 포인트와의 불일치에 적용됐어요. 로캘이 지정되지 않으면, 다른 모든 문자나 코드 포인트와 달리 이 문자들은 적용 중인 규칙에 따라 매우 다른 의미를 가질 수 있어요. (코드 포인트가 255보다 큰 문자는 유니코드 규칙을 강제해요. 반면 ASCII 문자의 규칙은 ASCII 규칙과 유니코드 규칙 아래에서 같아요.)

유니코드 규칙 아래에서는 이 상위-Latin1 문자들이 유니코드 코드 포인트로 해석돼요. 즉 Latin-1(ISO-8859-1)과 C1 제어 문자와 같은 의미를 가져요.

'ASCII 규칙과 유니코드 규칙'에서 설명했듯이, ASCII 규칙 아래에서는 이것들이 미할당 문자로 간주돼요.

이것은 예상치 못한 결과를 낳을 수 있어요. 예를 들어 문자열에 255보다 큰 코드 포인트가 추가되면 규칙이 ASCII에서 유니코드로 바뀌면서 문자열의 의미가 갑자기 바뀔 수 있어요. 예를 들어 다음 프로그램과 그 출력을 보세요:

$ perl -le'
    no feature "unicode_strings";
    $s1 = "\xC2";
    $s2 = "\x{2660}";
    for ($s1, $s2, $s1.$s2) {
        print /\w/ || 0;
    }
'
0
0
1

s1에도 s2에도 \w가 없다면, 왜 그들의 이어붙이기에는 있을까요?

이 이상현상은 유니코드를 쓰지 않던 옛 프로그램을 방해하지 않으려는 Perl의 시도와, 유니코드 지원을 매끄럽게 추가하려는 Perl의 욕구에서 비롯돼요. 하지만 그 결과는 매끄럽지 않은 것으로 판명됐어요. (참고로, 이런 일이 일어날 때 경고받길 선택할 수 있어요. encoding::warnings 참고.)

이 문제를 해결하기 위해 Perl v5.12부터 use feature 'unicode_strings'가 추가됐어요. 다음 것들에 영향을 줘요:

  • 문자열의 해석,
  • chr,
  • .\x{...},
  • 정규식 문자 클래스,
  • 이 외 여러 곳.

위에서 unicode_strings의 효과가 여러 Perl 릴리스에 걸쳐 증가했음을 알 수 있어요. (그리고 Perl의 유니코드 지원은 계속 개선돼요. 가장 완전하고 정확한 결과를 얻으려면 사용 가능한 최신 릴리스를 쓰는 것이 좋아요.) unicode_stringsuse v5.12 이상을 쓰면 자동으로 선택된다는 점을 주목하세요.

위에서 설명한 Perl보다 이전의 Perl, 또는 문자열이 unicode_strings의 스코프 밖에서 함수에 전달될 때는 다음 절을 보세요.

Perl에서 유니코드 강제하기 (Forcing Unicode in Perl, 또는 유니코드 강제 해제)

때때로('유니코드가 일어나지 않을 때'나 '"유니코드 버그"' 참고) 단순히 바이트 문자열을 UTF-8로 강제하거나 그 반대로 해야 하는 상황이 있어요. 표준 모듈 Encode를 이것에 쓸 수 있고, 저수준 호출인 utf8::upgrade($bytestring)utf8::downgrade($utf8string[, FAIL_OK])도 쓸 수 있어요.

utf8::downgrade()는 문자열에 바이트 하나에 맞지 않는 문자가 있으면 실패할 수 있다는 점을 주목하세요.

이미 원하는 상태인 문자열에 두 함수 중 아무것이나 호출하는 것은 no-op이에요.

'ASCII 규칙과 유니코드 규칙'이 문자열이 유니코드 규칙을 사용하게 되는 모든 방법을 줘요.

XS에서 유니코드 사용하기 (Using Unicode in XS)

XS 수준의 유니코드 소개는 perlguts의 "Unicode Support"를, API 세부사항은 perlapi의 "Unicode Support"를 참고하세요.

더 이른 유니코드 버전에서 작동하도록 Perl 해킹하기 (Hacking Perl to work on earlier Unicode versions, 아주 심각한 해커 전용)

Perl은 기본적으로 최신 지원 유니코드 버전을 내장하고 오지만, 목표는 이전 버전을 사용하도록 바꾸는 것을 허용하는 것이에요. 하지만 v5.20과 v5.22에서는 사용 가능한 가장 이른 버전이 유니코드 5.1이에요. v5.18과 v5.24는 모든 이전 버전을 처리할 수 있어요.

원하는 유니코드 버전의 파일을 유니코드 웹사이트 https://www.unicode.org에서 다운로드하세요. 이것들은 Perl 소스 트리에서 lib/unicore의 기존 파일을 대체해야 해요. 그 디렉터리 안의 README.perl의 지침을 따라 이름 일부를 바꾸고, then build perl(INSTALL 참고).

perl-5.6.X에서 코드 이식하기 (Porting code from perl-5.6.X)

5.8부터 시작하는 Perl은 5.6과 다른 유니코드 모델을 가져요. 5.6에서 프로그래머는 utf8 프라그마를 사용해 주어진 스코프가 유니코드 데이터를 다룰 것으로 예상한다고 선언하고, 유니코드 데이터만 그 스코프에 도달하도록 해야 했어요. 5.6에서 작동하는 코드가 있다면, 코드에 다음과 같은 조정 중 일부가 필요할 거예요. 예제는 코드가 5.6에서도 계속 작동하도록 작성돼 있어서, 시도해 보는 것이 안전해요.

5.6 모델에서는 시간이 지남에 따라, 5.8에서 좋지 않은 것으로 여겨진 것 몇 가지가 있습니다. 5.8 모델에서:

  • 몇 가지 다른 함수가 있습니다.
  • UTF-8을 직접 다루지 않아도 되는 경우가 많아요.

(구체적인 이식 조정 사항과 예제는 원문 perlunicode의 해당 절을 참고하세요.)

버그 (BUGS)

위의 '"유니코드 버그"'도 참고하세요.

확장과의 상호작용 (Interaction with Extensions)

Perl이 확장과 데이터를 교환할 때, 확장은 UTF8 플래그를 이해하고 그에 따라 행동할 수 있어야 해요. 그 플래그를 인식하지 못하면, 확장이 잘못 표시된 데이터를 반환할 가능성이 높아요.

그래서 유니코드 데이터로 작업한다면, 유니코드 데이터 교환에 문제가 있는지 사용하는 모든 모듈의 문서를 확인하세요. 문서가 유니코드에 대해 전혀 말하지 않는다면, 최악을 의심하고 아마 소스를 봐서 모듈이 어떻게 구현되는지 배우세요. 완전히 Perl로 작성된 모듈은 문제를 일으키지 않아야 해요. 다른 프로그래밍 언어로 작성된 코드에 직접이나 간접으로 접근하는 모듈은 위험해요.

영향을 받는 함수에 대해 데이터 손상을 피하는 간단한 전략은 교환되는 데이터의 인코딩을 항상 명시적으로 만드는 것이에요. 확장이 다룰 수 있다고 아는 인코딩을 선택하세요. 확장에 전달되는 인자를 그 인코딩으로 변환하고, 결과를 그 인코딩에서 다시 변환해요. 변환을 대신 해 주는 래퍼 함수를 작성해서, 나중에 확장이 따라잡으면 함수를 바꿀 수 있게 하세요.

예를 들어 유명한 Foo::Bar::escape_html 함수가 아직 유니코드 데이터를 다루지 않는다고 해요. 래퍼 함수가 인자를 원시 UTF-8로 변환하고 결과를 Perl의 내부 표현으로 다시 변환할 거예요:

sub my_escape_html ($) {
    my($what) = shift;
    return unless defined $what;
    Encode::decode("UTF-8", Foo::Bar::escape_html(
                                 Encode::encode("UTF-8", $what)));
}

때때로, 확장이 데이터를 변환하지 않고 저장하고 검색만 할 때는, 그렇지 않으면 위험한 Encode::_utf8_on() 함수를 쓸 수 있을 거예요. 유명한, C로 작성된 Foo::Bar 확장이 이런 프로토타입에 따라 데이터를 저장하고 검색하게 해 주는 param 메서드를 제공한다고 해요:

$self->param($name, $value);            # 스칼라 설정
$value = $self->param($name);           # 스칼라 검색

아직 어떤 인코딩에 대한 지원도 제공하지 않는다면, 이런 param 메서드를 가진 파생 클래스를 작성할 수 있어요:

sub param {
  my($self,$name,$value) = @_;
  utf8::upgrade($name);     # UTF-8 인코딩인지 확인
  if (defined $value) {
    utf8::upgrade($value);  # UTF-8 인코딩인지 확인
    return $self->SUPER::param($name,$value);
  } else {
    my $ret = $self->SUPER::param($name);
    Encode::_utf8_on($ret); # UTF-8 인코딩이라는 것을 앎
    return $ret;
  }
}

일부 확장은 DB_File::filter_store_key와 그 계열 같은 데이터 진입/탈출 지점의 필터를 제공해요. 확장의 문서에서 그런 필터를 찾아보세요. 유니코드 데이터로의 전환을 훨씬 더 쉽게 만들어 줄 수 있어요.

속도 (Speed)

일부 함수는 UTF-8 인코딩 문자열에서 작업할 때 바이트 인코딩 문자열보다 느려요. length(), substr(), index()처럼 문자를 뛰어넘어야 하는 모든 함수나 정규식 매칭은 기본 데이터가 바이트 인코딩일 때 훨씬 빠르게 작동할 수 있어요.

Perl 5.8.0에서는 그 느림이 종종 꽤 두드러졌어요. 5.8.1에서는 상황을 개선한 캐싱 체계가 도입됐어요. 일반적으로 UTF-8 인코딩 문자열로 하는 연산은 여전히 더 느려요. 예를 들어 \p{Nd} 같은 유니코드 속성(문자 클래스)은 [0-9] 같은 더 단순한 대응물보다 꽤 많이(5-20배) 느린 것으로 알려져 있어요. (다시 말하지만, Nd와 일치하는 유니코드 문자는 수백 개인 반면 [0-9]와 일치하는 ASCII 문자는 10개예요.)

더 알아보기 (Learn more)