linuxで文字コードを変換するコマンドのconvmv
もどきを作った。
・経緯
事の発端はレンタルサーバーに100MB以上のファイルを上げられなかったこと。zipを上げたかったのだが、サーバー側の制約で無理なようだった。
仕方ないので圧縮する前のディレクトリごとサーバー側に置いておいて適宜サーバー側で圧縮する方法を探した。...おおっ、PHPからzipコマンドが打てる、これは勝つる!
と思って嬉々として圧縮したら、文字化けした。
・圧縮圧縮ゥ
phpというのは便利なもので、execを使えば外部コマンドが実行できてしまう。zipを作るのだってたったこれだけで出来てしまうのだから驚きである。
<?php
$dir="./example";
$filename="archive.zip";
exec("zip -r $filename $dir",$ext_log,$state); //zipを作成
print_r($ext_log); //ログ出力
echo $state;
?>
・ところがどっこい
ところが、これで作ったファイルをWindowsで展開すると文字化けするのである。というのも、サーバー側はlinuxだからファイル名をUTF-8にして圧縮するのだが、WindowsはこれをShift-jisだと思って表示するからだ。Windowsは早く文字コードをUTF-8にしろ。
さて、どうすれば文字化けしないのか?
やや抽象的な話になるが、Shift-jisからUTF-8に変換する操作を\( f\)、その逆を\( f^{-1}\)とし、ファイル名を\( m\)とする。これはもともとWindowsで作ったファイルなので、\( m\)の文字コードはShift-jisである。
さて、これをサーバー側で圧縮するとき、linuxが文字コードをUTF-8に変換して圧縮する。したがって、圧縮された後のファイル名は\( f(m)\)になっている。これをWindowsで見ると、もともと\( m\)だったのが\(f(m)\)になったので、当然文字化けする。
ではどうすればいいのかというと、圧縮の前にファイル名をUTF-8からShift-jisに変換する、つまり\(m\)を\(f^{-1}(m)\)に書き換えておくのである。そうすると、圧縮後のファイルは\( f\left(f^{-1}(m)\right)=m\)となって、解凍したときもとのファイル名と一致するというわけだ。
・そのためのconvmv
じゃあ文字コードを変換するにはどうすればいいのかというと、linuxのコマンドconvmv
を使う。使い方は端折るが、変換前と変換後のエンコードを指定することでファイル名を変換してくれる。
<?php
$dir="./example";
exec("convmv -r -f utf-8 -t sjis $dir --notest",$ext_log,$state);
print_r($ext_log); //ログ出力
echo $state;
?>
これで出来たかな?とおもってステータスコードを見たら、127
(そんなコマンドねえよバーカ)が返って来ていてひっくり返った。インストールしないと使えないらしい... 安レンタルサーバー民にはない権限が必要なようだったので、convmv
を使うのは諦めた。
・かくなる上は
しょうがないからphpでconvmvもどきを作るしかない。幸いphpにはmb_convert_encoding
という文字コード変換関数が存在するので、これを使っていく。
大まかな手順としては、ファイル名を一つ一つmb_convert_encoding
にぶちこみ、変換前後で名前が変化しなければスキップ、変化していたら書き換える、といった感じになる。また、コマンドのオプション-r
(内部ディレクトリに対して再帰的に実行)の有無や、--notest
(これがないときは実際には書き換えない)の有無を選択できるようにした。
ソースコードはこんな感じになった。
<?php
$dir = "./example";
global $notest,$recu,$n_success,$n_fail,$n_tot;
$notest=false;
$recu=false; //再帰的実行
$n_success=0; //書き換え成功数
$n_fail=0; //書き換え失敗数
$n_tot=0; //チェックしたファイル数
function convmv($tdir){
global $notest,$recu,$n_success,$n_fail,$n_tot;
$dirh=opendir($tdir);
while($tfile=readdir($dirh)){
if($tfile[0]==='.')continue; //skip config file(such as .htaccess)
$tpath="$tdir/$tfile";
echo "Scanning: $tpath \n";
if($recu && is_dir($tpath)){
convmv($tpath); //再帰的実行
}
$n_tot++;
$cvtd=mb_convert_encoding($tfile,"sjis","utf8");
if($cvtd!==$tfile){
echo "Rename: $tfile -> $cvtd \n";
if($notest){
$cpath="$tdir/$cvtd";
$rn_stat=rename($tpath,$cpath);
if($rn_stat){
$n_success++;
echo "Rename Success.\n";
}
else{
$n_fail++;
echo "Rename Fail.\n";
}
}
}
}
closedir($dirh);
}
convmv($dir);
echo "Scan has Completed! Scanned: $n_tot \n";
if($notest)echo "Result: Success $n_success, Fail $n_fail \n";
?>
phpって便利ですね(n回目)