제공: 한빛 네트워크
저자: Xavier Noria, 추홍엽 역
원문: http://www.onlamp.com/pub/a/onlamp/2006/08/17/understanding-newlines.html
역주: 뉴라인(newline) - "새 줄"로 직역하기에는 다소 어색한 감이 있고, 우리말로는 "줄바꿈"이 가장 적합할 듯하지만 줄바꿈이라는 단어자체에 줄을 바꾼다는 의미가 포함되어 라인피드(Line Feed)나 캐리지 리턴(Carriage Return)과도 혼동될 우려가 있어 그냥 "뉴라인" 이라는 용어로 번역하였습니다.
프로그래머는 항상 텍스트를 다룬다:
% perl -wpe1 alice.txt
There was nothing so very remarkable in that; nor did Alice think
it so very much out of the way to hear the Rabbit say to itself,
`Oh dear! Oh dear! I shall be late!"
여기 형식을 잘 갖춘 세 줄의 텍스트가 있다. 이제 alice.txt 파일에는 실제로 무엇이 있는지 보자:
% perl -w0777e "print join ".", unpack "C*", <>" alice.txt
84.104.101.114.101.32.119.97.115.32.110.111.116.104.105.110.103.\
32.115.111.32.118.101.114.121.32.114.101.109.97.114.107.97.98.\
108.101.32.105.110.32.116.104.97.116.59.32.110.111.114.32.100.\
105.100.32.65.108.105.99.101.32.116.104.105.110.107.10.105...
단지 숫자로 된 코드 무리들이 줄지어 있다.
무슨 일이 일어나고 있는걸까? 컴퓨터는 오직 숫자만을 이해한다. 컴퓨터가 작동하는 방식은 이렇다; 숫자를 사용하여 텍스트를 인코딩한다. 이 숫자들을 텍스트로 해석하기 위하여 소프트웨어는 숫자와 캐릭터 간에 매핑을 한다. 예에서 사용된 맵--ASCII--은 숫자 84를 문자 "T"에 대응시키고, 숫자 104를 문자 "h"로, 숫자 101을 문자 "e"로 대응시키는 등의 방식으로 제정된다. 이러한 매핑을 기술적으로 캐릭터 인코딩이라 부른다. 많은 캐릭터 인코딩들이 존재하며 대부분은 ASCII의 확장이다.
이와는 반대로 다음과 같이:
print $fh "foo";
… Perl은 문자 "f", "o", "o"에 대응하는 코드들을 $fh로 프린트한다. 사실은, 이 문자들은 Perl 내부에서 이미 숫자들이다: 이것이 Perl이 내부적으로 문자열을 표현하는 방식이다.
하지만 모든 코드들이 문자에 대응하는 것은 아니다. 예를 들어, 여러분은 원래의 텍스트에는 많은 공백이 있는 반면 위의 예제에서는 숫자 리스트 내에 공백이 없는 것을 눈치챘을 것이다. 콘솔에서 어떻게 공간을 끝마치는 것일까? 그것들은 어디서 오는 것일까? 사실은 ASCII에서 숫자 32가 스페이스 문자에 대응하는 것이다. 텍스트를 프린트할때, 숫자 32를 만날 때마다 빈 지점이 나타난다. 이것은 숫자 84로부터 "T"가 나오는 것과 같은 원리이다.
이와 유사하게, 숫자 10이 나타날 때 Unix 터미널은 상쾌하게 새로운 라인을 시작하고 코드 변환을 계속 한다. 이러한 과정의 결과로 텍스트는 책처럼 렌더링된다. 그러나 이것은 환상이다: 파일이나 문자열 내에는 아무런 문자도 없고, 스페이스도, 분리된 라인도 없다.
뉴라인(newline)은 다른 모든 것들처럼 숫자를 사용하여 인코딩된다. 보여지는 방식에 대해 잊어라: 그것은 단지 코드일 뿐이다. 이것이 바로 컴퓨터에서의 뉴라인을 이해하는 키포인트이다.
"\n"은 무엇인가?
역사적인 이유들로 어떤 단일 코드도 뉴라인으로 명쾌하게 해석되지 않는다. ASCII코드 10이 기술적으로 "뉴라인"이라 불리지만 불운하게도 뉴라인의 실제 표현방식은 운영체제나 어플리케이션 컨텍스트에 의존한다.
ASCII 기반의 시스템에서 새 줄을 표현하는데 사용되는 코드는 다음과 같다.
- LF: 라인피드(Line Feed), "\cJ", 유니코드 000A, 아스키 코드 0x0A, 012, 10.
- CR: 캐리지 리턴(Carriage Return), "\cM", 유니코드 000D, 아스키 코드 0x0D, 015, 13.
- CRLF: CR 바로 다음에 LF가 오는 두 코드의 쌍. 순서 유지.
세 가지 서로 다른 규칙이 존재하는 것이다. 각각의 ASCII 기반의 플랫폼은 이 중 하나를 따른다.
- LF: Unix와 Unix계열의 시스템, Mac OS X, Linux, AIX, Xenix, BeOS, Amiga, RISC OS 등등.
- CR: Apple II family, Mac OS 버전 9 전체.
- CRLF: Microsoft Windows, WinCE, DOS, OS/2, CP/M, MP/M 등등.
만약 여러분이 이 세가지 군의 각각의 운영체제가 도는 3개의 컴퓨터에서 에디터를 열어서, 각각 x + 리턴키 + y를 치고 저장을 하면 디스크 상의 결과는 서로 다를 것이다. 앞서 사용한 것과 같은 방법을 따라보면 각각 다음의 결과를 확인할 수 있을 것이다.
- Ubuntu GNU/Linux: 120.10.121
- Mac OS 9: 120.13.121
- Windows NT: 120.13.10.121
텍스트 에디터에서는 이것이 보이지 않는다. Perl(혹은 이와 유사하게 작동하는 다른 프로그램 언어)로 어떻게하면 정확한 코드를 생성해 낼 수 있을까? 리눅스 상에서 "foo" 다음에 새 줄을 프린트한다고 생각해 보자. 위의 리스트에 따르면 여러분은 이렇게 쓸 것이다:
print "foo\012";
정답이다. 이제 윈도우에서 "foo" 다음에 새 줄을 프린트 하고 싶다면 어떻게 할까? 이론적으로는 저렇게 하는 대신에 다음과 같이 작성할 필요가 있다.
print "foo\015\012"; # 그러나 실제로는 맞지 않다!
한 걸음 더 나아가서 여러분이 작성하는 스크립트가 실행될 운영체제를 모를땐 어떻게 할까? OS에 독립적이어야 하는 프로그램을 작성한다고 상상해 보라. 즉, 프로그램을 다른 기종간에 이식가능하게 만들고 싶다고 말이다. 원칙적으로는 이와 비슷하게 작성할 필요가 있다:
if ($^O eq "darwin" || ...) {
print "foo\012";
} elsif ($^O eq "MSWin32" || ...) {
print "foo\015\012"; # 실제로는 참이 아니다. 다음을 보라.
} else {
print "foo\015";
}
보기 흉하지만, 단지 이렇게만 작성할 필요가 있도록 어딘가 은폐시켜 놓을 수 있다.
print "foo", newline_for_runtime_platform();
이것이 의미상으로 바로 "\n"이 의미하는 바이다. 정확히는 저렇게 구현하진 않겠지만, 이식가능한 방법으로써 뉴라인을 출력하기 위해 "\n"을 사용하는 것이 기본 생각이다; Perl은 각 시스템에서 무엇을 할지 알게 된다.
print "foo\n"; # 모든 시스템에서 올바르게 동작한다.
이것은 Perl과 같은 C 기반의 언어에서 공통적이다. 다른 언어들은 "\n"에 대한 서로 다른 의미론(semantics)을 가지고 있다. 예를 들어, Java에서는 "\n"은 이식 가능한 뉴라인이 아니다; Java에서 이식 가능한 방법으로 foo 다음 새 줄을 출력하기 위해서는 System.out.println("foo")과 같은 메소드 호출을 사용한다.
뒤에서 벌어지는 일들
앞의 절에서는 여러분에게 전체적인 그림을 그려주기 위해 정확성을 잃었다. 이제는 더 자세하게 배울 때가 됐다.
Perl에서의 \n에는 여러분이 명확하게 이해해야 하는 두 가지 중요한 사실이 있다:
- 문자열 상수 \n은 단 하나의 글자로 이루어져 있다. 항상, 어느 곳에서든지 그렇다. "foo\n"은 모든 시스템에서 길이가 4이다. 특히, "\n"은 윈도우즈의 CRLF가 아니다.
- 문자열 상수 \n은 Mac OS pre-X를 제외한 모든 시스템에서 "\012"와 같은 값이다. Mac OS pre-X에서는 "\015"와 같다.
다음에 주어진:
print $fh "foo\n";
은 윈도우상에서 제대로 작동한다. "\n"은 여기서는 진짜로 LF인데, 여러분은 $fh에서 어떻게 CRLF쌍이 정확하게 끝나게 되는지 의아해 할 것이다.
Perl은 C로부터 라인 종결자(terminator) 처리에 대한 접근방법을 상속받았다; Perl에는 모든 I/O 명령에 대한 책임을 지는 레이어가 있다. 5.8.0 버전 이후 기본값으로 그 레이어는 PerlIO이다. 여러분이 프린트를 하는 스크립트를 사용하면 PerlIO는 그 사이에 끼어들어 마법을 부린다: 만약 Perl이 CRLF 플랫폼상에서 돌고 있다면 그것은 스트림상의 모든 "\n"을 CRLF쌍으로 몰래 변환한다. 이것은 전적으로 몰래 일어나기 때문에 여러분은 알아채지 못한다.
윈도우 상에서 그러한 변환을 수행하는 C 코드는 perlio.c 에 정의된 PerlIOCrlf_write() 함수내에 있다.
if (*buf == "\n") {
/* ... */
*(b->ptr)++ = 0xd; /* CR */
*(b->ptr)++ = 0xa; /* LF */
/* ... */
}
위의 코드에 있는 "\n"은 Perl 문자열이 아닌 C 캐릭터임에 주의하라. 운좋게도 그러한 시맨틱은 동시에 일어나며 그러므로 조건문은 테스트를 해야 하는지를 테스트한다.
이러한 변환은 윈도우즈나 다른 어떤 CRLF 플랫폼 상에서는 텍스트 파일을 읽을 때 다른 방법으로 행해진다. 레이어는 어떠한 CRLF 쌍도 하나의 "\n"으로 바꿔버린다. 이것은 마찬가지로 perlio.c의 PerlIOCrlf_get_cnt()에서 일어난다.
if (nl < b->end && *nl == 0xd) {
test:
if (nl + 1 < b->end) {
if (nl[1] == 0xa) {
*nl = "\n";
c->nl = nl;
}
/* ... */
}
/* ... */
}
주의할 점은 이것은 오직 실제 CRLF 쌍만 다룬다는 것이다. 홀로 떨어져 있는 CR이나 LF는 건드리지 않는다.
그러므로 만약 여러분이 윈도우즈에서 표준 라인기반 while 루프문을 사용하여 텍스트 파일로부터 라인을 읽는다면:
while (my $line = <$fh>) {
# ...
}
아무런 CRLF 쌍도 $line으로 들어오지 않고 오직 LF만이 들어온다.
파일을 다루면서 일어나는 모든 마법은 파일과 관련되어 있다. 기본값으로 Perl은 CRLF 플랫폼에서 텍스트 모드로 파일을 여는데, 이것은 그 이상의 변환도 그 이하의 변환도 일어나지 않는다는 것을 의미한다. 여러분은 binmode()를 사용하여 이것을 해제할 수 있다. 다른 스트림-예를 들어 소켓 같은-은 기본값이 바이너리 모드이다.
이것이 왜 여러분이 윈도우즈 상에서 이미지 파일과 다른 모든 텍스트가 아닌 파일들을 바이너리 모드로 열 필요가 있는지에 대한 이유이다. 뉴라인에 대한 이러한 규칙들은 단지 정규 텍스트 파일에 대한 것일 뿐이다. 다시 말해, PNG 이미지에 사용된 바이트들에는 아무 관계가 없다. 원리상 PNG 이미지는 어딘가에 우연히 다른 의미로 CRLF 쌍을 가질 수도 있다. 만약 여러분이 PNG 이미지를 읽기 위해 여는데 파일 핸들에서 바이너리 모드를 세팅을 안한다면, IO 레이어는 그 깜짝 변환을 실행할 것이며 몇몇 바이트들을 걸러내서 데이터를 방해할 것이다. 쓰기 시에도 같은 상황이 발생한다. 만약 여러분이 MP3 포맷으로 된 노래를 표현하는 바이트들의 버퍼를 가지고 있는데 이것을 윈도우즈 상에서 텍스트 모드로 디스크에 쓴다면, 모든 0xa는 앞에 0xd가 삽입될 것이고 그 MP3는 쓰레기가 되고 말 것이다.
한편, :crlf PerlIO 레이어 덕분에 어떤 플랫폼에도 상관없이 CRLF와 "\n"로의 변환을 활성화 할 수 있다:
open my $fh, "<:crlf", "alice.txt" or die $!;
이런 작은 트릭으로, 스크립트는 네이티브 규칙이든 CRLF든 텍스트를 이해할 수 있다.
"이식성"이란 무엇인가?
뉴라인이 실행되는한, 이식가능한 프로그램은 텍스트 데이터의 뉴라인 규칙이 그것이 실행되는 플랫폼의 것이라는 가정을 하고 잘 수행된다.
그러한 규칙들은 아마도 몇몇 알려지지 않은 운영체제가 돌아가는 일부 머신에서는 오직 런타임 시에만 알 수 있다. 이것은 악몽일수도 있겠지만 운좋게도, 좋은 언어들은 여러분이 이것을 수고를 들이지 않고 해결할 수 있는 방법들을 제공한다. 항상 이식가능한 방식으로 작성하는 것이 좋은 방법이다.
Perl에서 뉴라인을 출력하기 위해 "\n"을 사용하라. 텍스트 파일과 관련된 파일 핸들링을 하는 라인 기반 루프문에는 <>를 사용하고 그렇지 않으면 리스트 컨텍스트 내에서 라인들을 불러들인다. 한 줄의 텍스트에서 뉴라인을 제거하려면 chomp()를 사용하라. 라인 종결자, 그리고 라인 경계에 대한 단언(assertions)을 위한 ^와 $을 매칭하려는 정규 표현식에서는 "\n"를 사용한다. 필요하면 /m을 사용한다.
바이너리 파일과 관련된 파일 핸들링에는 binmode를 세팅한다. 텍스트와 바이너리 파일을 구분하지 않는 시스템에서 개발을 할 때 조차도 그렇게 한다. 보통 binmode는 seek()/tell() 과 read()을 하려 할 때도 필요하다. 그것은 read()가 텍스트 모드에서는 CRLF 플랫폼에서 CRFL를 "\n"로도 변환하기 때문에 그렇다. 그러나 seek()/tell()는 그렇게 하지 않으며 바이트 오프셋도 다를 수 있다.
"\n"가 "\012"라고 가정하지 말라. 소켓 프로그래밍에서의 공통적인 함정이다. 그것은 필요적으로 참에 머물지 않는다. 예를 들어, 가공되지 않은 HTTP 헤더를 생성하기 위해 CRLF 쌍이 필요하다면 종결자로 "\r\n"을 사용하지 말라. 이 문자열의 의미는 시스템에 의존한다. 대신에 "\cM\cJ" 에서 처럼 그것을 하드코딩한다. 더 좋은 것은 :crlf 익스포트 태그를 통해 Socket.pm에 의해 제공된 변수들인 $CR, $LF, $CRLF를 사용하는 것이다. 이러한 것들이 이식가능한 해결방안이다.
그러나 이식성은 어떠한 종류의 텍스트라도 받아들일 수 있는 준비를 의미하지는 않는다. 인수로 전달된 파일의 라인수를 세는 이식가능한 Perl 스크립트를 작성할 필요가 있다고 가정해 보자:
my $lines = 0;
++$lines while <>;
print "$lines\n";
이 프로그램은 정확하고 이식가능하다. 이것은 읽기에서 라인 종결자를 다루고, 다이아몬드 연산자에게 위임하며, "\n"을 통하여 이식가능한 방법으로 뉴라인을 출력한다.
물론, 이것이 가능한 모든 상황에서 동작하는 것을 의미하지는 않는다. 예를 들어 어느날 동료가 여러분에게 MacBook을 가지고 다가와 스크립트가 몇주동안은 그럭저럭 작동하더니 갑자기 다중라인을 가진 몇몇 파일들이 단일 라인을 가졌다고 보고된다고 말하고 있다고 가정해 보자. 만약 여러분이 뉴라인이 어떻게 작동하는지 이해한다면 몇분만에 고칠 수 있을 것이다. 그러나 그렇지 않다면 헤매게 될 것이다.
그 문제는 분명 입력 파일이 뉴라인으로 LF를 사용하고 있지 않음에 틀림없는데 그것이 Mac OS X에서의 규칙이다. Vim, Emacs, TextMate와 같은 편집기들은 사용자가 뉴라인을 설정할 수 있게 하기 때문에 항상 위험성이 있다. Mac OS X에서는 다이아몬드 연산자가 LF를 찾는다. 만약 그 파일이 CR을 사용한다면 Mac OS X 규칙을 따라 전체 파일은 단일 라인으로 인식되는 것이다. 이것은 관찰된 행동과 일치하며 여러분의 해독이 될 것이다.
파일이 CRLF를 사용했다면 계산된 라인 수는 Mac OS X에서 맞게 될 것이라는 점에 주의하라 (우연이지만 맞다).
규칙이 일치하지 않을 경우
유비쿼터스 네트워킹으로 인해 런타임 플랫폼에서 사용되는 것보다 훨씬 많이 다른 줄바꿈 규칙을 가진 텍스트를 볼 상황들이 생겼다.
여러분이 FAT32 파티션으로 된 리눅스에서 파일을 읽는다면, 아마도 CRLF 쌍을 보게 될 것이다. 만약 기본값으로 설정된 Cygwin의 nano를 사용하여 쓰여진 파일을 DOS 콘솔에서 읽는다면 Unix형식의 뉴라인을 보게 될 것이다.
FTP나 서브버전을 포함한 버전관리 시스템과 같은 몇몇의 파일 전송 도구들은 이러한 문제들을 인식하여 필요한 관리작업을 수행한다. 윈도우즈 XP에서 작업하는 개발자가 Perl로 된 소스 파일을 서브버전 저장소에 체크인을 하고 또다른 개발자가 Debian이 돌고 있는 랩탑에서 이를 체크아웃하면, 서브버전은 뉴라인을 다루어 그것들을 변환한다. 각각의 로컬 사본들은 현재 시스템에 대응하는 규칙을 갖게 된다.
여러분이 이메일 첨부파일이나 tarball, Jabber서버, 블루투스, 메모리 스틱이나 혹은 다른 어떤 보조 수단을 통해 파일을 받아보면, 뉴라인 정규화가 되어 있지 않을 것이다. 이러한 것의 전형적인 예가 윈도우즈에서 생성되어 몇몇의 Unix flavor로 작업하는 개발자에게 이메일로 가는 로그 파일이다. 개발자가 pager에서 로그를 열어보면 라인들의 끝에 이상한 ^M들을 볼 수가 있다. 도대체 무슨 일일까?
무슨 일이 일어나고 있는지 이해하기 위해 그 원리를 알아보자. 그 파일은 윈도우즈에서 생성되었기 때문에 CRLF를 뉴라인 규칙으로 사용했다. 개발자는 유닉스 뉴라인의 규칙으로 LF에서 라인이 바뀔꺼라고 기대하며 유닉스 pager로 파일을 열어본다. 운좋게도 각 쌍에 LF가 있어서 pager에서는 거의 정확하게 보인다. 아무도 제거하지 않은 쓸모없는 CR도 있다는 것만 빼면 말이다. 그 글자가 pager에서 ^M으로 보여지는 글자이며 그것이 라인 끝에서 일어난 일의 이유이다.
한 줄짜리 명령으로 이것을 고치기 위해서는 다음이 필요하다:
perl -pi.bak -we "s/\015\012/\012/" error.log
리눅스에서 PerlIO는 뉴라인을 가지고 아무것도 하지 않으며 그러한 제어 문자들은 건드리지 않은 채 통과될 것이다. 리눅스 시스템에서 하나의 "\012"는 라인 종결자를 의미하기 때문에 각 $_ 반복문은 "\015\012"로 끝난다. 명백히, 여러분이 할 필요가 있는 전부는 이 "\015"를 지우는 것이다.
그러나 가끔 어떤 규칙에도 상관없이 텍스트를 다루고 싶을 때가 있다. 그리고 아마도 여러 규칙들이 혼합되어 있을때도 그렇다. 예를 들면, 견고한 CGI프로그램은 텍스트가 다른 머신에서 오기 때문에 텍스트 영역의 일부 텍스트에 regex를 적용하기 위하여 뉴라인을 정규화할 수도 있다:
{
my $ALT_NL = "\n" eq "\012" ? "\015" : "\012";
sub normalize_newlines {
my $text = shift;
$text =~ s/\015\012|$ALT_NL/\n/go;
return $text;
}
}
어떤 파일에서 사용된 뉴라인 규칙을 알 필요가 있었다면 이렇게 시작한다:
my $filename = shift;
open my $fh, "<", $filename or die $!;
binmode $fh; # CRLF 플랫폼에서 라인 종결자를 건드리지 않고 통과시킨다.
$/ = \1024; # 최대로 많은 바이트를 읽는다.
my $buf = <$fh>;
close $fh;
my $convention = "unknown";
if ($buf =~ /\015\012/) {
$convention = "CRLF (Windows)";
} elsif ($buf =~ /\012/) {
$convention = "LF (Unix)";
} elsif ($buf =~ /\015/) {
$convention = "CR (Mac pre-OSX)";
}
print "$convention\n";
이 글이 목적을 이룬다면 여러분은 그 즉시 이 프로그램을 이해하게 될 것이다.
유니코드에서의 뉴라인
유니코드 표준은 뉴라인을 위한 다른 코드들도 정의한다: CR (Carriage Return, 000D), LF (Line Feed, 000A), CRLF (Carriage Return과 Line Feed, 000D,000A), NEL (Next Line, 0085), FF (Form Feed, 000C), LS (Line Separator, 2028), PS (Paragraph Separator, 2029).
유니코드는 입력시 이 코드들 모두를 인식할 수 있는 프로그램을 권고하는데, 그리 적합한 요구사항은 아니며 아직 보통의 시도는 아니다. 어쨌든 Perl을 포함한 많은 언어들이 아직은 이러한 권고사항을 따를 수 있는 쉬운 방법을 제공하고 있지 않다. Perl의 readline 연산자는 레코드의 범위를 결정하기 위해 $/를 사용하는데, 이것이 그에 대한 대안을 나타낼 수는 없다.
부분적인 해결책은 확장된 정규화기(normalizer)를 사용하는 것이다.
{
my $ALT_NL = "\n" eq "\012" ? "\015" : "\012";
sub normalize_newlines_for_unicode {
my $text = shift;
$text =~ s/\015\012|$ALT_NL|\x{0085}|\x{000C}|\x{2028}|\x{2029}/\n/go;
return $text;
}
}
임의의 유니코드 텍스트에 대하여 라인 기반 루프문을 에뮬레이트 할 수 있다:
{
local $/ = "\012"; # ensure we don"t halve CRLFs
while (my $LF_line = <$fh>) {
open my $gh, "<", \normalize_newlines_for_unicode($LF_line);
{
local $/ = "\n";
while (my $line = <$gh>) {
# ...
}
}
}
}
그러나 이것은 엉터리다. 예를 들어, 텍스트에 LS만 사용할 경우 바깥의 루프문은 첫번째 반복에서 그것을 모두 없앨 것이다. 일반적인 해결책은 그러한 가능성을 허용할 수 없다. 게다가 원래의 뉴라인 문자들을 다 날려 버릴런지도 모른다. 그리고 그것이 유니코드 텍스트를 가지고 작업을 할 때 여러분이 원하는 것일 수도 있고 원하지 않는 것일 수도 있다.
더 나은 방법은 입력 문자를 문자단위로 처리하는 로우 레벨의 해결법이다. PerlIO 레이어가 그 예다.
출력에 대하여, "알맞게" 뉴라인 문자를 매핑하는 것이 권고사항인데 조금은 애매모호하다. 여기에 대한 가이드 라인으로서, 정규 뉴라인에 대해 보통처럼 단지 "\n"을 출력하기만 할 수 있다.
Perl의 정규 표현식에서 고정자 ^와 $는 아직 LS와 그 나머지 것들을 처리하지 않는다. 아마도 언젠가는 처리하게 될 것이다. 한편으로 NEL과 LS, PS는 유니코드 문자열에서 \s와 일치한다.
대체로 이러한 뉴라인 코드들은 실전에서는 다소 새로운 것들이며 분명 잠시동안은 "바깥 세상으로" 나올 것 같아보이진 않는다.
감사의 말
PerlIO에 대해 알려준 Nick Ing-Simmons사와 이 글의 초안을 교정해준 Enrique Nell, 그리고 유니코드에 대해 조언을 해준 Jarkko Hietaniemi와 Sadahiro Tomoyuki에게 감사의 말을 전하고 싶다.
참고
Xavier Noria는 Perl 전문가이며 동적 언어에 대한 열혈론자이다.