ほげほげ見聞録

技術メモ、備忘録、使い方はそのうち覚える

Zip圧縮時にファイル名が文字化け

Zipと文字コード周りで色々と悩まされたので、そのエラーと解決方法など。

エラー発生 その1

とあるシステムで、ZipファイルをDLすると中身のファイルが文字化けしている。

状況

処理の流れは、ごにょごにょの理由で以下の通り。
保管フォルダ→一時フォルダにDL用に名前変更したファイルを移動→Zip圧縮→DL

原因・解決方法

そもそも何故そうなるのか。
ファイル名の文字コードが、Linuxではロケールによる(今回はUTF-8)、WindowsではShift-JIS、と異なる。
この為、圧縮時と解凍時の環境が異なる場合は文字化けする。

今回の場合、使用環境はWindowsが多いとのこと。
Windows側にパッチを当てる方法もあるが、それをクライアント全てに行ってもらうのは良くないとかなんとか。
なので、Zip圧縮前に「convmv」でファイル名をShift-JISに変換して対応した。

convmv -r -f utf8 -t sjis --notest ' 【圧縮対象ファイル名】

https://j3e.de/linux/convmv/man/j3e.de




エラー発生 その2

上で解決と思いきや、別のエラー。
ZipファイルDL時、圧縮対象のファイル名に機種依存文字「①」「Ⅲ」などが含まれるとZipファイルが空になっている。

状況

  • WindowsでDLするようにファイル名をShift-JIS変換して圧縮
  • 文字化けする一時保存した対象ファイルはファイル名がUTF-8のまま

原因

ZipファイルDL処理の流れは以下の通り。
一時フォルダにコピー(UTF-8)→「convmv」でファイル名変換(Shift-JIS)→名前変換後のファイルをZip圧縮

つまり、ファイル名をShift-JISに変換できなかった為、圧縮対象ファイルが存在せず、Zipが空になると思われる。

convmvの実行結果確認

Linux上でconvmvの結果を確認してみる。
「--notest」オプションがあると問答無用で実行されるので、これを外す。

convmv -r -f utf8 -t sjis ' 【圧縮対象ファイル名】

DL可能な日本語ファイル名

mv "folder/ほげ.jpg"  "folder/?ー.jpg"
No changes to your files done. Use --notest to finally rename the files.

DL不可能な日本語ファイル名

shiftjis doesn't cover all needed characters for: "folder/①.jpg"
To prevent damage to your files, we won't continue.

「①.jpg」の方はShift-JISに変換できないエラーが発生している。

解決方法

以下のURLやその他調べてみると、機種依存文字「①」「Ⅲ」はShift-JISに存在しない。
charset.uic.jp

なので、Shift-JISに機種依存文字を独自に実装した文字コードを使う。
Windowsだと、CP932、MS932、Windows-31Jと呼ばれる。

convmvで扱える文字コードの一覧取得は以下の通り。

convmv --list

「cp932」があるのでこれを使用。

convmv -r -f utf8 -t cp932 --notest ' 【圧縮対象ファイル名】

一時フォルダ内のファイル「①.txt」はCP932のファイル名に変換でき、Zip圧縮できるようになった。

ちなみに、今まで使用していたsjisは一覧になかったが、一覧にあるshiftjisと変わりない。

文字コード参考。
qiita.com

5C問題

当然ながら5C問題は起きるので、プログラムごとに対応する必要あるのが悲しい現実。
今回は「にゃーん!」な感じで対応した。根本的な解決方法があるといいのになぁ。
https://ja.wikipedia.org/wiki/Shift_JIS#2.E3.83.90.E3.82.A4.E3.83.88.E7.9B.AE.E3.81.8C5C.E7.AD.89.E3.81.AB.E3.81.AA.E3.82.8A.E3.81.86.E3.82.8B.E3.81.93.E3.81.A8.E3.81.AB.E3.82.88.E3.82.8B.E5.95.8F.E9.A1.8Cja.wikipedia.org


余談

今回のシステムはPHPだったので、文字コード変換にmb_convert_encoding()も使用している。
convmvを使わずmb_convert_encoding()でCP932に変換するとどうなるか、気になったので以下を実験。
「ほげ.txt」に対して以下二通りの方法でファイル名をSJIS-win(CP932)に変換してみた。

mb_convert_encoding()でファイル名変換・移動したファイルの情報

# stat ?ー.txt
  File: `?\260.txt'

移動後にconvmvで変換したファイルの情報

# stat ?ー.txt
  File: `\202?\260.txt'

FTPなどで見たファイル名は同じ(こんな感じの「ق」アラビア語)だったが、Linux上では実はファイル名が違う。
convmvで変換したファイルの方は頭に「\202」が付属している。他の日本語でも何かしら「\【三桁数字】」がつく。

とりあえず考えてみた。
Shift-JIS「ほげ」をUTF-8で表示すると、「U+DC82(0355 0262 0202 )U+0642(0202)U+DCB0(0355 0262 0260)」となる。
真ん中はアラビア語で両脇が下位サロゲートに含まれている。
不正なUTF-8と判断され、PHP側で消えてしまったのかもしれない。
https://ja.wikipedia.org/wiki/UTF-8#.E3.82.B5.E3.83.AD.E3.82.B2.E3.83.BC.E3.83.88.E3.83.9A.E3.82.A2.E3.81.AE.E6.89.B1.E3.81.84ja.wikipedia.org
https://tools.ietf.org/html/rfc3629tools.ietf.org
http://www.utf8-chartable.de/unicode-utf8-table.plwww.utf8-chartable.de

ちなみに、PHPでいう「SJIS-win」は「CP932」と等価なんだそうで。
http://php.net/manual/ja/mbstring.encodings.phpphp.net

記事を書くにあたって参考になったのは「プログラマのための文字コード技術入門」。
エラー解決は急ぎなこともありネット情報で対応してしまったが、記事作成時に確認したら探していた情報があっさり載っていた。
他にも、対応時はLinuxUTF-8でファイル名符号化していると思っていたが、ロケールによる・日本ではEUC-JPが多いなどの情報に気が付いた。
ネットも良いけど紙文献もちゃんと調べようと思い直しましたとさ。