以前から気になっていたのですが、
- Windows上で、ファイル名に日本語を使っているファイルをzip形式でアーカイブして、
- FreeBSDに持っていて、
- FreeBSD上でunzipコマンドを使って展開すると、
ファイル名が文字化けすることがあります。たとえば、こんな具合。
Fedora CoreなLinuxマシンでためしても同じ文字化けをしたので、FreeBSDだけの問題ではないようです。
また、日本語のファイル名を使っていると100%常に文字化けするわけでもなく、ちゃんと日本語のファイル名で展開できることがあります。
文字化けする場合、その原因にはいくつもありまして、たとえば、ファイル名はShift-JISエンコーディングになってしまって、画面上では文字化けして見えてしまうこともあります。そのときは、たとえばconvmvというツールを使って(FreeBSDだとports/converters/convmvからインストールできます)、ファイル名の文字コード変換をすれば、正しい文字に直すことができます。
今回話題にとりあげた文字化け現象は、convmvを使ったところで、どうやっても正常なファイル名に戻せないような文字化けです。hdコマンド(16進ダンプ)を使って、正常な場合と、unzipしたあとの場合のコード、両方を見比べて、「こりゃ~わけわかんねーな」と結論に至った、文字化け現象です。
ネット検索してみると、そういった話題がちらほら見つかったので、unzipでファイル名が文字化けするのは、すでによく知られた問題のようです。
たとえば、これが参考になりました。
http://mail.nl.linux.org/linux-utf8/2005-06/msg00010.html
SUMMARY: Zip/Unzip and encoding problem (Was: Re: How to detect the encoding of a string?)
どうすればいいのか、ズバリな解決方法はのっていませんでしたので、これを手がかりに、ちょこっとunzipのソースコードを探ってみまして、一応、自分なりの解決方法を見つけました。ちょこっと細工をしてunzipをコンパイルすることで、文字化けを防ぐことができました。
☆ ☆ ☆
上記のページからリンクされている、
http://mail.nl.linux.org/linux-utf8/2005-06/msg00006.html
によれば、unzipが、わがままにファイル名の文字コード変換をしているとのことで、しかもこのコード変換をやめさせるようなオプションは存在しないらしいです。
じゃあ、ソースコードをかきかえちゃえっ!・・・というわけで、さっそくunzipのソースを見てみました。FreeBSDでportsを使っていると、「make patch」のコマンド1発でソースが展開されるので、便利ですねぇ。
とりあえず、文字コード変換をどこでやっているのか、あたりをつけるために、まずは、convとかlatin(←上記のWebページにでていた単語)とか、ありがちな単語でgrepしまくってみます。
くさい場所が絞り込まれてきたら、伝家の宝刀「printfデバッグ」メソッドを適用し、場所を特定します(笑)。
・・・というような、洗練されていない方法ではありますが、今回は、わりとすぐに見つかりました。その場所とは・・・
unzpriv.hというファイルで定義されている、Ext_ASCII_TO_Nativeというマクロでした。
/* Convert filename (and file comment string) into "internal" charset.
* This macro assumes that Zip entry filenames are coded in OEM (IBM DOS)
* codepage when made on
* -> DOS (this includes 16-bit Windows 3.1) (FS_FAT_)
* -> OS/2 (FS_HPFS_)
* -> Win95/WinNT with Nico Mak's WinZip (FS_NTFS_ && hostver == "5.0")
* EXCEPTIONS:
* PKZIP for Windows 2.5, 2.6, and 4.0 flag their entries as "FS_FAT_", but
* the filename stored in the local header is coded in Windows ANSI (CP 1252
* resp. ISO 8859-1 on US and western Europe locale settings).
* Likewise, PKZIP for UNIX 2.51 flags its entries as "FS_FAT_", but the
* filenames stored in BOTH the local and the central header are coded
* in the local system's codepage (usually ANSI codings like ISO 8859-1).
*
* All other ports are assumed to code zip entry filenames in ISO 8859-1.
*/
#ifndef Ext_ASCII_TO_Native
# define Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr) \
if (((hostnum) == FS_FAT_ && \
!(((islochdr) || (isuxatt)) && \
((hostver) == 25 || (hostver) == 26 || (hostver) == 40))) || \
(hostnum) == FS_HPFS_ || \
((hostnum) == FS_NTFS_ && (hostver) == 50)) { \
_OEM_INTERN((string)); \
} else { \
_ISO_INTERN((string)); \
}
#endif
これを見ると、どのソフトでZIPアーカイブを作成したか、など、ある種の条件によって、コード変換をするかしないかを切り替えているので、冒頭で述べたように、文字化けしたりしなかったり、というのも納得です。ちなみに、文字化けしたZIPアーカイブは、Windows XPで、NTFS上のファイルを、Lhaplusでアーカイブしたものでした。
なお、このマクロが使われているのは、fileio.cの中の、このあたりです。
if (option == DISPL_8) {
/* translate the text coded in the entry's host-dependent
"extended ASCII" charset into the compiler's (system's)
internal text code page */
Ext_ASCII_TO_Native((char *)G.outbuf, G.pInfo->hostnum,
G.pInfo->hostver, G.pInfo->HasUxAtt,
FALSE);
/* translate the Zip entry filename coded in host-dependent "extended
ASCII" into the compiler's (system's) internal text code page */
Ext_ASCII_TO_Native(G.filename, G.pInfo->hostnum, G.pInfo->hostver,
G.pInfo->HasUxAtt, (option == DS_FN_L));
if (G.pInfo->lcflag) /* replace with lowercase filename */
STRLOWER(G.filename, G.filename);
さてさて、というわけで、このマクロを無効にしてしまえば、ファイル名の文字コード変換が行われなくなります。マクロ定義の内容部分をコメントアウトしてもいいですし、#ifndefでガードが書かれていますから、
#ifndef Ext_ASCII_TO_Native
の行の手前に、
#define Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)
を入れちゃうってのもいいかもしれません。
ソースコードを変更しないおしゃれな方法としては、コンパイラの-Dオプションを使って、-D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)'というように、マクロ定義をしてしまう方法もあります・・・というか、今回GCCのinfo(正確にはCPPのinfo)を調べてみてはじめて知ったんですが、-Dオプションで、引数つきのマクロ定義もできたんですね。shellにカッコとかの文字が食べられちゃうことがあるので、そのへんをうまくエスケープさせるなど配慮が必要なようです。
FreeBSDでportsを使ってコンパイルする場合(/usr/ports/archivers/unzip/)、こんなコマンド1発で、文字コード変換を無効にしたunzipをビルドできます。
% make "CFLAGS+=-D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)'"
参考までにコンパイル中のログ。
===> Extracting for unzip-5.52_3
=> MD5 Checksum OK for unzip552.tar.gz.
=> SHA256 Checksum OK for unzip552.tar.gz.
===> Patching for unzip-5.52_3
===> Applying FreeBSD patches for unzip-5.52_3
===> Configuring for unzip-5.52_3
===> Building for unzip-5.52_3
NOTE: use bsd target for non-Intel FreeBSD compiles (if any).
make unzips CC="cc" LD="cc" AS="cc" CF="-Wall -I. -DASM_CRC -DUNIX -DBSD -D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)' -DUSE_UNSHRINK" AF="-Di386 -D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)' -DUSE_UNSHRINK" CRC32=crc_gcc
cc -c -Wall -I. -DASM_CRC -DUNIX -DBSD -D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)' -DUSE_UNSHRINK unzip.c
cc -Di386 -D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)' -DUSE_UNSHRINK -x assembler-with-cpp -c -o crc_gcc.o crc_i386.S
cc -c -Wall -I. -DASM_CRC -DUNIX -DBSD -D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)' -DUSE_UNSHRINK crctab.c
マクロを無力化させたため、fileio.cをコンパイルするときに、warningがでてますね。
cc -c -Wall -I. -DASM_CRC -DUNIX -DBSD -D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)' -DUSE_UNSHRINK fileio.c
fileio.c: In function `do_string':
fileio.c:2062: warning: statement with no effect
fileio.c:2144: warning: statement with no effect
こうやってビルドしたunzipを試してみると・・・文字化けしなくなりました。
ここでは一応、nkfで文字コード変換して、見た目で文字化けしないようにしています(unzipして展開されたファイルのファイル名はShift-JISになっていました)。
あと、細かいことは気にしないように。
☆
これで副作用が発生しないか自信がないので、unzip-noconvという別の名前で、インストールすることにしました。
この問題ってSolarisについてるunzipでも同じことが起きるんですよねぇ。
返信削除私が試した限りでは、XP標準機能でzip圧縮したものは問題なかったのですが、lhasaで圧縮したものはNGでした。
まぁzipにするならascii文字以外使うなよぉってことなのかもしらんですが(^^;)
同じ問題で困っていました。
返信削除環境はlinuxですが同様に解決できました。ありがとうございます。
distribution: debian-etch-i386
unzip_version: unzip-5.52