Windows で作成された ZIP が Linux とか Mac OS で解凍すると文字化けする問題。確実なのは Wine 経由で 7-Zip などの Windows 用アーカイバを使うことなんだけど、「Wine をインストールできない」という状況に置かれた場合を想定してみた。
unzip
や 7z
などを使って解凍する場合、LANG
の値によって展開のされ方が異なるので LANG=C
を忘れずに設定しておく。トップディレクトリが文字化けしていると困るので親ディレクトリを用意する。
(unzip
の場合、アーカイブによっては LANG=ja_JP.UTF-8
のときに正しい名前で解凍できることもあるので LANG=C
が悪影響を及ぼすことがある)
# unzip LANG=C unzip -d tmp ARCHIVE.zip # 7z LANG=C 7z x -otmp ARCHIVE.zip
ファイル名の確認だけであればアーカイブリストの出力を iconv
で変換するだけ。
LANG=C zipinfo ARCHIVE.zip | iconv -f CP932 -t UTF-8
展開済みの場合は find
の結果を iconv
に渡す。
find tmp | iconv -f CP932 -t UTF-8
で、スクリプトを作ってファイルをリネームしていこうと思ったけどちょっと問題があった。
- 既に UTF-8 な文字列に
iconv -f CP932 -t UTF-8
をかけると更に文字化けする find
の結果を単純に利用しようとするとディレクトリ名が変わって再帰的なリネームができなくなる
前者については iconv -t UTF-8
のように入力のエンコーディングを省略すると UTF-8 の場合は終了コード 0
、CP932 の場合にエラーになるのでこれを利用する。
後者についてはファイルとディレクトリを別々に処理すればいいわけだけど、<CP932>/<CP932>/<CP932>.txt
のように複数の階層に渡って CP932 になっているとファイルをリネームしようとしたときにディレクトリ名まで変わってしまうためディレクトリを移動して相対パスで書き換えなくてはいけない。また、下層からリネームしていかないとこれもまたエラーになる(これは幸い、Linux の find
は階層順で表示してくれるので tac
を使って逆さまにするだけでいいはず)。
iconvert.sh
#!/bin/sh -e rename () for f do srcname=`basename "$f"` printf '%s' "$srcname" | iconv -t UTF-8 >/dev/null 2>&1 && continue || : dstname=`printf '%s' "$srcname" | iconv -f CP932 -t UTF-8` cd "`dirname "$f"`" mv -v "$srcname" "$dstname" done PREFIX=`cd "$1" && pwd` ### Replacing files ### ifs=$IFS IFS=' ' set -- `find "$PREFIX" -mindepth 1 ! -type d` IFS=$ifs rename "$@" ### Replacing directories ### ifs=$IFS IFS=' ' set -- `find "$PREFIX" -mindepth 1 -type d | tac` IFS=$ifs rename "$@"
このスクリプトに親ディレクトリを渡す。
iconvert.sh tmp
--iconv
に対応している rsync
でも変換はできるけどコピーになるから無駄かなぁ。自分でスクリプトを書くよりは確実だとは思うけど。
rsync -av --iconv=sjis,utf-8 tmp/ dstdir/
Wine が使えない状況を想定してみたけどやっぱり Wine を使うのが確実だと思った。