2008/05/05(月)【Linux/FreeBSD】 ファイル名の文字コード変換ツール

Linux/FreeBSDやその他Unixにおいてファイル名の文字コードをEUC-JPからUTF-8(またはその逆)に変更したいとき、良い方法がありませんでした。

この変更を自動的に行うPerlスクリプトです。

使い方

ソースにあるスクリプトをテキスト等で保存し、fs-charconv.pl として実行権限をつけてください。通常 root での操作になると思います。*1

# ./fs-charconv.pl (現在の文字コード) (変更する文字コード) [処理するディレクトリ]

処理するディレクトリ以下のすべてのディレクトリに対して文字コードを変更します。省略した場合はカレントディレクトリ以下のすべてのファイルの文字コードを変更します。

途中中段などにより、同一ファイルに二重に実行すると大変なことになります。ご注意ください。また、実行前に壊れてもいいファイルとディレクトリを用意して必ずテストをしてください。

EUC-JPからUTF-8へ
# ./fs-charconv.pl euc-jp utf8 ./
SHIFT-JISからUTF-8へ。/home以下すべて
# ./fs-charconv.pl sjis utf8 /home
UTF-8からEUC-JPへ
# ./fs-charconv.pl UTF-8 euc-jp

文字コードの指定方ですが、一般的な書き方ならどう書いても大丈夫です。Perlの自動認識機能(文字コードエイリアス)が強力に働きます。

*1 : 自分で使用して問題がないことは確認済ですが、万が一トラブルが発生してもGPLv3に従い、自己責任です。

ソース

GPLv3にて配布します。fs-charconv.pl などで保存してください。Perl5.8以降が必要になります。

#!/usr/bin/perl
#-----------------------------------------------------------------------------
# File name charset converter
#			Licensed under GPLv3
#			(C)2008 nabe / nabe@abk
#-----------------------------------------------------------------------------
use strict;
use Encode;
use Encode::Guess qw(euc-jp shiftjis iso-2022-jp);
my $EXTRA_UTF8_PATCH=1;

if (!defined $ARGV[1]) {
	print "$0 from-code to-code [dir]\n";
	exit(0);
}
my $from = $ARGV[0];
my $to   = $ARGV[1];
my $base = $ARGV[2] || "./";
&dir_find($base, \&rename_filter);
exit(0);

sub rename_filter {
	my $dir   = shift;
	my $fname = shift;
	if ($fname !~ /[\x80-\xff]/) { return $fname; }

	my $new_fname = &from_to($fname, $from, $to);
	rename($dir.$fname, $dir.$new_fname);
	print "convet to :$dir$new_fname\n";
	return $new_fname;
}

#-----------------------------------------------------------------------------
# ファイル検索
#-----------------------------------------------------------------------------
sub dir_find {	# 再起関数
	my ($dir, $filter) = @_;
	if ($dir eq '') { return; }
	if (substr($dir, -1) ne '/') { $dir .= '/'; }
	my $files = &search_files( $dir );
	foreach(@$files) {
		my $file = $_;
		$file =  $dir . &$filter( $dir, $file );
		if (-d $file) { &dir_find( $file, $filter ); }
	}
}
#------------------------------------------------------------------------------
# ●ファイルを検索する
#------------------------------------------------------------------------------
sub search_files {
	my $dir=shift;

	my $fh; # = Symbol::gensym();
	opendir($fh, $dir) || return [];
	my @filelist;
	foreach(readdir($fh)) {
		if ($_ eq '.' || $_ eq '..' )  { next; }	# ./ ../ は無視
		push(@filelist, $_);
	}
	closedir($fh);
	return \@filelist;
}

#------------------------------------------------------------------------------
# ●日本語変換
#------------------------------------------------------------------------------
sub from_to {
	my ($str, $from, $to) = @_;
	my $ex_patch = $EXTRA_UTF8_PATCH;

	# 元の文字コードがUTF-8
	if ($ex_patch && $from =~ /UTF.*8/i) {
		$str =~ s/\xEF\xBD\x9E/\xE3\x80\x9C/g;	# ~ EFBD9E E3809C
		$str =~ s/\xEF\xBC\x8D/\xE2\x88\x92/g;	# ‐ EFBC8D E28892
		$str =~ s/\xE2\x88\xA5/\xE2\x80\x96/g;	# ∥ E288A5 E28096
	}
	if ($from =~ /UTF.*8/i) {	# from が UTF8 のとき
		Encode::_utf8_on($$str);
		eval { $str = Encode::encode($to, $str); };
	} else {
		eval { Encode::from_to($str, $from, $to); };
	}
	if ($ex_patch && $to =~ /UTF.*8/i) {
		$str =~ s/\xE3\x80\x9C/\xEF\xBD\x9E/g;	# ~ E3809C EFBD9E
		$str =~ s/\xE2\x88\x92/\xEF\xBC\x8D/g;	# ‐ E28892 EFBC8D
		$str =~ s/\xE2\x80\x96/\xE2\x88\xA5/g;	# ∥ E28096 E288A5
	}
	return $str;
}