読者です 読者をやめる 読者になる 読者になる

😃 mattintosh note 📝

Hello Raspberry Pi!

🐧 Linux で ZIP from Windows の文字化け修正

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 を使うのが確実だと思った。