Perl/한글 페이지의 소스 보기 (부분)
마지막으로 [b]
-- Loading page list... --
내용출력
로그인[l]
Diary
[f]
최근변경내역
[r]
페이지목록[i]
횡설수설[2]
게시판[3]
링크
수정할 수 없습니다: Perl/한글 는 읽기 전용 페이지입니다.
=== # 기타 === '''1) 펄 코드의 인코딩''' 윈도우즈에서 텍스트 에디터를 열어서 펄 스크립트를 작성하고 저장하면, 보통은 윈도우에서 사용하는 기본 인코딩인 cp949로 인코딩되어서 저장될 겁니다. 이 스크립트를 윈도우에서 실행하면 실행환경(명령 프롬프트 창이라거나, GUI라거나) 역시 같은 인코딩을 쓰니까 별 무리 없이 입력과 출력이 되는 것처럼 보입니다. 그러나 실제로는 우리가 1장과 2장에서 봤듯이, 한글이 "문자열"이 아니라 "바이트 덩어리"로 취급되고 있기 때문에, 문자열 처리를 할 때 직관적인 처리가 힘들어집니다. 또한 다른 인코딩을 사용하는 시스템과 데이타를 교환할 경우 호환에 문제가 생길 우려도 있습니다. 다른 분들은 어쩌시나 모르겠는데 (이 글을 보시면 리플로 좀 알려주시면 좋겠습니다), 저는 그런 이유로 제가 작성한 펄 스크립트 코드는 UTF-8로 인코딩하여 저장하고, use utf8; 프라그마를 포함하고, 출력할 때는 STDOUT에 인코딩을 지정하는 형태로 씁니다. (다만 이 경우 윈도우에서 perldoc 내_스크립트.pl 해서 POD 도움말을 볼 때 문제가 되더군요) '''2) 파일 이름 인코딩''' 이 연재에서 파일을 읽거나 쓰기 위해서 열 때는 항상 파일 이름이 영문이나 특수문자, 숫자로만 되어 있는 걸로 했습니다. 그런데 파일이름이 "내사진.jpg" 와 같이 한글이 포함되어 있다면 어떻게 될까요? 파일을 열기 위해서는 디스크에서 찾아야 합니다. 디스크에는 어느 폴더에 들어있는 파일들의 이름 목록이나, 어느 파일이 디스크의 어디에 있는지 저장되어 있습니다. 문제는 이 때 파일 이름이 저장될 때도 역시나 특정한 인코딩 규약에 맞춰서 바이트 덩어리로 바뀌어 저장된다는 겁니다. (애초에, 펄 프로그램 바깥에서 벌어지는 모든 일들은 바이트 덩어리를 조작한다고 간주하셔도 무방할 겁니다) 그러면 파일 이름이 디스크에 저장될 때는 어떤 인코딩 규약을 따르는가? * 역시나 한글 윈도에서는 cp949를 따릅니다. (실제로는 내부적으로는 아닐 수도 있습니다만, 적어도 cp949에 맞춰 파일 이름을 검색하면 찾을 수 있습니다) * 리눅스/유닉스에서는 사용자의 지역 정보, locale 설정에 따라 다릅니다. 예전에는 euc-kr이었고, 요새는 UTF-8이 대세입니다만, 사용자가 환경 변수 설정에 따라 달라질 수 있습니다. 따라서 파일을 열 때, 파일명 역시 해당 인코딩 규약에 맞춰서 인코딩된 상태여야 합니다. 예를 들어보겠습니다. 제 폴더에 문서 두 개가 있습니다. * 하나는 "한글문서_cp949.txt"이고, 그 안에는 한글텍스트가 cp949 인코딩으로 저장되어 있습니다. * 또 하나는 "한글문서_utf-8.txt"이고, 그 안에는 한글텍스트가 UTF-8 인코딩으로 저장되어 있습니다. 이 두 파일을 읽어서 내용을 출력하는 코드입니다. 지난 장에서 다뤘던 내용들이 동시에 등장합니다. {{{#!vim perl number #!perl # 16_filename.pl # 이 스크립트는 반드시!! UTF-8로 저장되어야 함 use strict; use warnings; use autodie; use Encode; use utf8; binmode STDOUT, ":encoding(cp949)"; { my $filename = encode("cp949", "한글문서_cp949.txt"); open my $in, "<:encoding(cp949)", $filename; while ( <$in> ) { chomp; print "[$_][", length($_), "]\n"; } close $in; } { my $filename = encode("cp949", "한글문서_utf-8.txt"); open my $in, "<:encoding(utf-8)", $filename; while ( <$in> ) { chomp; print "[$_][", length($_), "]\n"; } close $in; } }}} * 8번 라인에서, use utf8 프라그마를 사용했습니다. 따라서 이하 코드에서 문자열 상수는 전부 디코딩된 "문자열"로 처리됩니다. * 10번 라인, 명령프롬프트 창에서 출력할 거라서 표준출력의 인코딩을 cp949로 지정했습니다. * 12-20번 라인에서는 "한글문서_cp949.txt" 파일을 열어서 읽습니다. * 13번 라인, 파일명이 코드 내에 그대로 적혀 있는데, 위에서 utf8 프라그마를 썼으니 이 파일명은 "문자열"입니다. 이 파일을 디스크에서 찾기 위해서는, 파일명을 "바이트 덩어리"로 바꿔서 운영체제에 부탁해야 합니다. 따라서 인코딩해야 합니다. 윈도우즈는 cp949 인코딩이 기본이므로, cp949 규약에 맞춰 인코딩합니다. * 14번 라인, 파일을 엽니다. 그런에 이 파일의 "내용"이 cp949로 인코딩된 한글 텍스트라고 그랬습니다. 따라서 읽을 때 cp949에 따라서 디코드하라고 레이어를 지정해줍니다. * 15번 라인에서 읽고 * 17번 라인, 읽은 라인의 내용과 길이를 출력합니다. * 22-30번 라인에서는 "한글문서_utf-8.txt" 를 열어서 읽습니다. * 23번 라인, 13번 라인과 마찬가지로 파일명을 인코드해야 하는데, 역시나 cp949 로 인코드해야 합니다. (UTF-8이 아닙니다!) 파일의 내용과 무관하게 파일명은 시스템에서 정해진 규약에 맞춰 인코딩되어 있기 때문입니다. * 24번 라인, 이 파일을 읽을 때는 내용을 UTF-8 에 따라서 디코드하라고 레이어를 지정해야 합니다. * 나머지는 앞과 동일 실행해보겠습니다 Upload:encoding17.png 처음 두 줄은 cp949 문서, 다음 두 줄은 UTF-8 문서를 읽고 출력한 내용입니다. 제대로 읽었고, 제대로 문자열 길이를 측정했고, 제대로 명령 프롬프트 창에 출력하고 있습니다. 위 코드에서는 utf8 프라그마를 썼기 때문에 일부러 파일명을 다시 인코딩해야 했습니다만, 다음과 같은 경우도 있을 수 있습니다. * utf8 프라그마를 쓰지 않고 스크립트를 그냥 cp949 로 저장했다면, $filename 은 encode() 하지 않고 그냥 문자열 상수를 써도 될 겁니다. 문자열 상수가 결국 바이트 덩어리인 상태니까요 * 파일 이름을 사용자가 키보드로 입력한다면, 입력이 들어올 때 cp949 상태로 들어올 테니 따로 encode() 하지 않고 그대로 써도 될 겁니다. (물론 표준입력을 자동으로 디코드하고 있는 상태라면 다시 인코드해줘야겠죠) '''3) 기존 데이타의 인코딩 알아맞추기''' 1장부터 지금까지는, 우리가 항상 "들어올 입력이 어떤 인코딩으로 저장되어 있는지 미리 알고 있다"고 가정했습니다. 그래서 해당 인코딩 규약을 decode()할 때 명시했습니다. 때로는 입력이 어떤 인코딩으로 되어 있는지 알 수 없을 때가 있습니다. * 내가 만든 파일이 아니라면 그 파일의 내용이 어떤 인코딩으로 되어 있는지 알 수 없습니다. * 네트워크로 전송받은 내용은 내가 알 수 없습니다. * 내 컴퓨터가 아닌 다른 시스템에서 수행될 경우는 그 시스템이 표준입출력 또는 파일이름을 어떻게 인코딩하는 지 알 수 없습니다. (이 경우는 환경 변수 등을 검사해서 알아내면 되겠습니다만) * 기타 등등, 암튼 내가 만든 게 아니면 모르는 게 일반적입니다. 이런 경우, 데이타와 별개로 그 데이타에 사용된 인코딩 정보가 전달된다면 모르겠지만 이걸 항상 기대할 수도 없습니다. 어떤 데이타가 무슨 인코딩으로 되어 있는건지 정확히 알 수 있는 방법이 있는가? 이 질문에 대한 답은 저도 모릅니다. 게다가 저도 확신은 없습니다만, 어떤 인코딩은 아예 정확히 알아내는 게 불가능할 수도 있습니다. 예를 들어 한글을 인코딩하기 위한 euc-kr 과 일본어 문자를 인코딩하기 위한 euc-jp의 경우 거의 동일한 형태로 문자와 코드간에 매핑이 되어 있어서... 인코딩된 데이타를 보면서 "이게 한국어를 euc-kr로 인코딩한 건지, 일본어를 euc-jp로 인코딩한 건지" 구분하는 게 어려운 걸로 압니다. 하지만 데이타의 인코딩을 "추측"할 수 있는 방법은 있습니다. 그 중 제가 아는 것은 Perldoc:Encode::Guess 코어 모듈입니다. 자세한 것은 perldoc 문서의 synopsis 를 보면서 얘기하도록 하겠습니다: (전체 문서는 http://perldoc.perl.org/Encode/Guess.html ) {{{#!vim perl number # $data 안에 잘못된 값이 들어있지 않은게 확실하다면 use Encode; use Encode::Guess qw/euc-jp shiftjis 7bit-jis/; my $utf8 = decode("Guess", $data); my $data = encode("Guess", $utf8); # 이건 동작하지 않는다! # 좀 더 정교한 방법 use Encode::Guess; my $enc = guess_encoding($data, qw/euc-jp shiftjis 7bit-jis/); ref($enc) or die "Can't guess: $enc"; # 에러를 잡아냄 $utf8 = $enc->decode($data); # 또는 $utf8 = decode($enc->name, $data) }}} * $data 변수에 어떤 바이트 덩어리가 담겨 있는 상태입니다. * 4번 라인, Encode::Guess 모듈을 불러오는데, 이 때 뒤에 "조사할 대상 인코딩"의 이름을 나열해 줍니다. 위에서 말했듯이 어떤 인코딩은 서로 구분이 불가능(하거나 아주 어렵거나)하며, 따라서 모든 인코딩을 다 찾아줄 수는 없고, 사용자가 지정한 인코딩들 중에 어느 인코딩에 해당하는지를 추측해줍니다. 기본적으로 ascii / utf8 / UTF-16 with BOM / UTF-32 with BOM 네 가지는 조사 대상에 포함이 되어 있습니다. 여기에 추가로 조사할 인코딩 목록을 적어주는 겁니다. 위 예제에서는 일본어를 인코딩하는 데 사용되는 세 가지 인코딩 방법을 나열했습니다. * 5번 라인, 이제 $data 의 내용을 디코드해서 추상적인 "문자열"로 변환하여 $utf8 이라는 변수에 넣습니다. Encode 모듈에서 사용하는 decode() 함수를 쓰는데, 인코딩 이름을 넣는 첫번째 인자 자리에 "Guess"라고 넣어줍니다. 그러면 $data 의 인코딩을 추측해서, 찾아낸 인코딩 규약에 맞춰 디코드해 줍니다. * 6번 라인은 역으로, 문자열을 인코드해서 바이트 덩어리로 만드는 예인데, 주석에도 나와 있지만 이건 실제로는 동작하지 않습니다. 상식적으로, 문자열을 인코드할 때는 내가 어떤 규약에 맞추어 인코드할지를 지정해줘야 하는데, 그걸 지정하지 않고 "추측해서" 인코드하라는 게 말이 안 됩니다. 이렇게 해서 $data 를 디코드할 수 있습니다만, 문제는 $data 에 들어가 있는 값이 제대로 된 값이 아니라서, (예를 들어 어느 인코딩에서도 생성될 수 없는 바이트값이 들어가 있다던가) 인코딩을 정확히 알아내지 못하는 경우입니다. 이런 에러 처리를 위해서 이왕이면 9번-14번 라인에 나온 것처럼 사용하는 것이 좋습니다. * 9번 라인에서 Encode::Guess 모듈을 로드하는데, 이번에는 체크할 "인코딩 후보"를 명시하지 않았습니다. * 10번 라인, 모듈에서 제공하는 guess_encoding() 함수를 쓰는데, 이 때 두번째 인자로 인코딩 후보 리스트를 넘겨줍니다. * 11번 라인, 10번 라인에서 만일 인코딩을 추측해내는데 성공하면, $enc 에는 해당 데이타에 사용된 인코딩 규약에 대한 정보가 담겨 있는 레퍼런스가 들어갑니다. ref()는 인자가 레퍼런스(대상이 무엇이든)인지 아닌지를 검사합니다. 참이면 레퍼런스의 대상의 타입이름을 반환하고, 레퍼런스가 아니라면 빈 스트링을 반환합니다. 따라서 이 값을 검사해서 추측에 성공했는지를 알아낼 수 있습니다. 여기서는 추측에 실패하면 die합니다. * 12번 라인, 추측에 성공한 경우는 12번 라인 또는 14번 라인에서와 같이 하여서 디코드할 수 있습니다. 이걸 응용해서, 지금까지 연재에서 사용했던 입력 파일들의 인코딩을 추측하는 프로그램을 만들어 보겠습니다: {{{#!vim perl number #!perl # 17_guess.pl use strict; use warnings; use autodie; use Encode; use Encode::Guess; use utf8; binmode STDOUT, ":encoding(cp949)"; my $filename = $ARGV[0]; open my $in, "<", $filename; while ( my $line = <$in> ) { chomp $line; my $enc = guess_encoding( $line, qw/cp949/ ); if (not ref($enc)) { print "인코딩 추측 실패\n"; next; } print "[", $enc->name, "]"; my $str = $enc->decode($line); print "[$str]\n"; } }}} * 18번 라인, 파일의 각 라인을 읽어서 그 라인이 무슨 인코딩으로 되어 있는지를 추측합니다. 이때 조사할 후보는 utf-8을 비롯한 기본후보에 추가로 cp949 까지입니다. * 20-23번 라인, 추측에 실패하면 그 라인은 포기하고 다음 입력 라인으로 통과 * 25-27번 라인, 추측에 성공하면 그 인코딩의 이름을 출력하고, 그 다음 라인의 내용을 디코드하고, 그걸 출력합니다. 이때 출력은 명령 프롬프트 창으로 나오니까 cp949로 인코딩하도록 10번 라인에서 지정했습니다. 실행결과를 보겠습니다. Upload:encoding18.png 먼저, cp949로 인코딩되었던 입력파일을 읽도록 했습니다. * 첫번째 라인은 영문밖에 없기 때문에, ascii 코드로 인코딩했다고 추측합니다. * 두번째부터 네번째 라인까지는 cp949로 인코딩되었다는 걸 맞췄습니다. 그 다음은 UTF-8로 인코딩되었던 입력파일을 읽도록 했습니다. * 역시 첫번째 라인은 ascii 로 추측했고 * 나머지 라인은 utf8 이라고 제대로 맞추었고, 그에 따라 제대로 디코드하고 다시 cp949로 변환하여 출력했습니다. 만약 18번 라인에서 체크할 후보를 다음과 같이 했다면 {{{#!vim perl 18 my $enc = guess_encoding( $line, qw/cp949 euc-kr/ ); }}} Upload:encoding19.png cp949로 인코딩된 문서의 경우는 추측에 실패했다고 나옵니다. 지난 장에서 얘기했듯이 cp949와 euc-kr은 거의 동일한 규약이라서 (cp949는 euc-kr에 없는 문자가 추가로 더 들어가 있음), 이 둘을 구분하지 못합니다. '''4) 서로 다른 인코딩 간의 변환''' 우리가 지금까지 다룬 내용은, "바이트 덩어리"를 "문자열"로, 또는 그 반대로 변환하는 것이었습니다. 때로는, A라는 규약으로 인코딩된 바이트 덩어리를, B라는 규약으로 인코딩된 바이트 덩어리로 바꿔야 할 때가 있습니다. 뭐 여기까지 읽으셨다면 쉽게 하실 수 있겠습니다만... 그냥 코드만 보여드리겠습니다 {{{#!vim perl # $data 에는 cp949로 인코딩된 바이트 덩어리가 들어있음 # 이걸 UTF-8로 인코딩된 형태로 바꾸고자 함 my $string = decode("cp949", $data); # 일단 디코드해서 문자열로 만들고 my $newdata = encode("UTF-8", $string); # 이 문자열을 UTF-8로 인코드 # $string 이라는 중간 변수를 쓰지 않고 한번에 my $newdata = encode( "UTF-8", decode("cp949", $data) ); # 아니면 아예 $data 자체를 바꿔버려도 된다면 from_to( $data, "cp949", "UTF-8" ); }}} Encode 모듈에서 제공하는 from_to()라는 함수가 처음 등장했습니다. '''5) Perl 내부에서 "문자열"을 저장하는 방법''' 이 섹션은.... 몰라도 지금까지 다룬 내용을 써먹는데는 큰 지장 없겠습니다만, 이왕 여기까지 온 김에... 게다가 Perl 의 다국어 처리 문제 때문에 여러가지 문서를 보다보면 이 얘기가 나올 것이기 때문에, 미리 맛뵈기로 다룹니다. (저도 자세히는 모릅니다) 우리가 디코드를 하면 이제는 바이트 덩어리가 추상적인 "문자열"로 변환된다고 했습니다. 그런데 여전히 이 "문자열"도 컴퓨터 메모리 안에 저장되기 위해서는 결국 0또는 1의 조합으로 나타나야 합니다. 그러면 이때는 어떻게 나타낼까, 즉 "Perl 내부에서는 문자열을 어떤 형태로 변환해서 보관해둘까"라는 의문이 들 수 있습니다. 간단히 말씀드리면 * UTF-8 인코딩 규약에 따라 인코드하고, (일부 시스템에서는 다른 규약을 사용) * 이 데이타가 Perl 내부에서 다루는 문자열이라는 걸 별도로 표시해 둡니다. (이 표시를 UTF8 flag 라고 합니다) 특히나 여러 Perldoc 문서에서, 이 "문자열"을 가리켜서 "utf8 형태"라고 말하기도 해서 좀 혼동스러운 면이 있습니다. "utf8"은 따라서 * 어떤 경우는 그냥 "UTF-8" 인코딩 규약을 나타내는 다른 이름이기도 하고 * 어떤 경우는 "UTF-8로 인코딩되어 있고 UTF8 flag 가 켜져 있는 상태로 저장되어 있는 문자열" = "펄 내부 문자열" = "디코딩된 상태의 문자열" 을 의미하기도 합니다. 상황과 문맥에 맞춰 읽어야 합니다 :-) 좀 더 자세한 내용은 [http://aero.springnote.com/pages/1053508 aero님의 "Unicode in Perl"]을 참조하세요. '''6) 마치며''' 다국어를 컴퓨터에서 표현하기 위한 여러가지 표준과 규약은... 학자와 엔지니어(그리고 아마도 경제인들과 정치인들까지)들이 수십년에 걸쳐 현재까지 논의하며 만들어낸 것이다보니... 어쩌면 어려운 게 당연합니다. 저도 Perl을 처음 접하고 펄로 홈페이지를 만들면서 한글 처리에서 난관에 부딪혔을 때, 이걸 해결해보겠다고 Encode 모듈 문서를 비롯해서 이것저것 읽으려고 시도해봤으나, 용어부터 시작해서 저 규약에 관한 이해가 안 되어 있다보니 perldoc 문서들의 내용도 전혀 못 알아듣겠더군요. 그래서 읽다가 포기하고, 몇 달 후에 다시 맘먹고 읽으려다가 포기하고... 그러길 지금까지 반복하고 있습니다. (솔직히 말해서 이번 연재에서 언급한 perldoc 문서 중에 제가 제대로 처음부터 끝까지 읽은 건 Encode::Guess 모듈 문서뿐입니다. Encode, utf8 이런 문서들은 아주 일부만 읽었지요. 나머지 부분은 안 읽었다기보다는 못 읽겠더라고요 이해가 안 되어서ㅠㅠㅠㅠ) 게다가 Perl은 오래전 버전에서 다국어를 지원하기 위한 모듈이나 문서가 현재 버전까지 내려오고 있어가지고, 정확히 무슨 문서부터 읽어야 큰 그림이 그려지고 최근 경향을 따라갈 수 있는지, 지금도 모르겠습니다. 이 문제 때문에 다른 분들도 종종 곤란을 겪고 질문을 올리시기도 하고 그런데... 매번 제대로 된 문서, 특히나 실제 사용에 직접적으로 참조할 수 있는 일목요연한 문서, 가장 중요한 건 한글로 적혀 있는(^^;) 문서가 있으면 좋겠다고 생각해 오면서, 저도 아는 것도 부족하고 여유도 없고 해서 손가락만 빨고 있었습니다. 이 Perl과 한글 연재가 그런 문서 중에 작게나마 일부가 될 수 있으면 좋겠습니다. 내용에 오류가 있다거나 보충할 만한 것의 지적은 언제든지 환영합니다.
Perl/한글
페이지로 돌아가기 |
다른 수정본 보기