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

バッチ処理で複数ファイルを一気にリネームするPerlスクリプトを書いた

perl

昨日の業務中、「普段はあまり遭遇しないがまれに生じる面倒な手作業」に遭遇し、「あ〜この作業、前から専用スクリプトで一括で済ませたいと思ってたんだよな〜」と思ったので、一旦作業の手を止めて猛然とコードを書いてみたら、それで問題をクリアできてしまった。すごい。

普段ならわざわざ作業の手を止めてまでコードを書くなんてことはなくて、「あ〜これコードで自動化したい……けどそんなの作り始めたらいつ終わるかわかんないから、残念だけど今はとりあえず手でやってしまって、あとで時間ができたらこれをお題に書いてみよう〜」なんて後回しにするものの、実際には当の問題が解消されてしまうとなかなかそのモチベーションも湧かなくて、結局いつまでたっても「あ〜これいつもの面倒なやつ……でも今はとりあえず手でやっておいて……」などと同じつらさの繰り返しに陥るところ、どうもこの数ヶ月は自分で書いたツールを使う機会が以前よりだいぶ増えてきたので、やればできるかなあ、という淡い自信に後押しされてガッと作ってみた。

やったことは大まかに言って、最近自分の中で流行ってる「ファイルのリネーム」なのだけど、以前にここで紹介した同類のツールだと、
note103.hateblo.jp

あまり大がかりな変更はできないというか、基本的には数文字ちょろっと変えたい、みたいな用途で生きてくるものなので、今回の目的からはちょっとズレていた。

じゃあ今回の目的って何だったのか、ということを少し具体的に説明すると、たとえば以下のようなファイル群があるとして、

01-06 りんご.mp3
2 ばなな.m4a
14-03 みかん.mp3
58 れもん.txt
108 ぶどう.md

これを以下のようにリネームしたい、みたいな。

1_りんご.mp3
2_ばなな.m4a
3_みかん.mp3
4_れもん.txt
5_ぶどう.md

ファイルの種類(mp3, txt, mdなど)はそのままで、名前もほとんど同じなんだけど、変更する際の規則性が弱いので、これを上記のツールでやろうとするとそれはそれで面倒というか、この状況ならコード使わずに手でやっちゃったほうがマシ……という感じにどうしてもなってしまう。

とはいえ、上記はあくまでサンプルなのでほんの数ファイルしかないけれど、昨日の本番状況ではこういうリネームしたいファイルが20点以上あったので、これ……このまま手でやるのは不毛すぎる……頑張って終わらせてもあまり達成感なさそう……というか人間がやってはいけない……などと思ってコードに向かってみたという。

実践編

以上のような前提を踏まえて、まず考えたのは、今回は以前のような標準入力方式(ターミナルに対話形式で打ち込むやつ)ではなく、リネーム後の文字列を事前にファイルに記述しておいて、それを読み込ませて動かすバッチ処理方式でいくべきだろう、ということ。

で、そのためにすぐ着手できそうなのは、とりあえず変換後のテキストファイル(バッチファイル)を作ること。

手順としては、上述のこの情報をもとに、

01-06 りんご.mp3
2 ばなな.m4a
14-03 みかん.mp3
58 れもん.txt
108 ぶどう.md

これが書かれたファイルを作る。

1_りんご.mp3
2_ばなな.m4a
3_みかん.mp3
4_れもん.txt
5_ぶどう.md

ということで、とりあえずFinderで該当ファイルを選択&コピーして……

f:id:note103:20160518004435p:plain

エディタにペーストすると……

f:id:note103:20160518004501p:plain

おっと……なにこの崩れ……。

って、ああ〜、それか〜……って。
以前、ここでちらっと呟いたのだけど、

いつも『CPANモジュールガイド』でお世話になっていますトミールさんの以下のブログ記事で解説されている、「UnicodeのNFDという正規化」の問題みたい。

Macのファイル名はNFDされている

MacOSXでは文字コードutf-8エンコーディングが使われているのだけど、UnicodeのNFDという正規化がされている。

具体的には、「だ」みたいな濁点つきカナが、「た」+「゛」の二つのUnicodeで表現されたりしてる。゛は「てん」で変換してでる濁点マーク(U+309B)ではなく、Unicodeで組み合わせる用として用意されている点(U+3099)。

Macだと時々これに遭遇して、ぐむむ……ってなる。
他のケースでありがちなのが、そのTwitterでも書いているけどPDFからテキストをコピペした時とか。

で、じつは今回の件においては、この崩れは見なかったことにして進めてしまってもとくに問題なかったりするのだけど、上記のトミールさんの記事をもとにこの崩れを直すコードも書いてみた。

実行している様子*1

f:id:note103:20160518004821g:plain

閑話休題。ではあらためて、そのように抜き出したテキストを元に、変更後のテキストを作成。

1_りんご.mp3
2_ばなな.m4a
3_みかん.mp3
4_れもん.txt
5_ぶどう.md

そいつをconv.txtとかにしておいて……このような変換スクリプトを書いてみた。

動かしてみると……。

f:id:note103:20160518005217g:plain

ん、あ、ちょっと待った。

from:
	01-06 りんご.mp3
	108 ぶどう.md
	14-03 みかん.mp3
	2 ばなな.m4a
	58 れもん.txt
to:
	1_りんご.mp3
	2_ばなな.m4a
	3_みかん.mp3
	4_れもん.txt
	5_ぶどう.md

微妙にファイルの順番が違う……。

どうやら上(from:)は辞書順、つまり行頭の数字が小さい順に並んでしまっていて、数値(108とか14とか)の順番で並んでいない。
しかし前掲のようにMacのFinderで見ると、こっちは数値順で並んでるわけで……

f:id:note103:20160518004435p:plain

これを元に変換後のリストを作ったからズレたみたい。紛らわしい……けど、もうここは僕には良い方法が見つからなかったので、諦めてバッチファイルの方をそれに合わせて並べ直すことに。

こんな感じで。

conv.txt
1_りんご.mp3
5_ぶどう.md
3_みかん.mp3
2_ばなな.m4a
4_れもん.txt

ん〜、バッドノウハウ的で腑に落ちないが、これで中身がメチャクチャになることはないはず。
で、あらためて動かしてみると……。

f:id:note103:20160518005640g:plain

OK!
って、これだとちゃんと中身が同じで名前だけ変わったのかどうか、わかりづらいですが、まあ大丈夫でした。(雑)

ちなみに、元々の目的はリネームなので、元ファイル自体の名前を変えても良かったのだけど、上記のようにバッチファイル(conv.txt)の指定が間違っていた場合、元ファイルが消えてしまうとけっこう面倒なので、元ファイルはそのまま残して、目的のファイル群は別ディレクトリ内に別名でコピーするようにしています。

これなら、万一指定を間違えてもコピー先を削除して、バッチファイルを修正後にまた処理すればよいので。

あと、バッチファイル上のファイル名と、コピー元のファイル群の数が異なる場合は、指示が間違っている可能性が高いので、処理する前にdieするようにしています。

たとえば、元のファイル群が以下で(前掲ママ)、

01-06 りんご.mp3
2 ばなな.m4a
14-03 みかん.mp3
58 れもん.txt
108 ぶどう.md

バッチファイルをこんな感じにしておくと……(6番を追加)

1_りんご.mp3
5_ぶどう.md
3_みかん.mp3
2_ばなな.m4a
4_れもん.txt
6_かき.txt

こんな感じに。

f:id:note103:20160518005738g:plain

ちゃんと途中で終わってくれる。

その他、現状だとコピー先のディレクトリにすでにコピー予定のファイル名が存在していたら上書きしてしまうので、それを避けるようにしたほうがいいんじゃないかとか、あるいは現在のコードだとmp3をテキストファイルにしたり、Excelファイルを.pdfにしたりとかいう奔放なことを平気でやるので(もちろん中身はメチャクチャになる)、拡張子をチェックしてファイルの種類が違ったらエラーを出すとかの機能も考えたのだけど、今回の仕様としては完全なリネームではなくコピーなので、そこまでやらなくていいか、と。

なにより、この内容でそもそもの目的だった20数点のファイル一括リネームもできたので、ひとまずヨシとしたいと思います。

追記/リファクタリング

コメント欄にて、いつもお世話になっております @xtetsuji さんから、

  • glob関数の使い方
  • ファイル群を読み込む際に数値順で統一する方法

などについて教えて頂きました。

ありがとうございます!!

ということで、それらを参考に以下のように直してみました。

修正版コード

batch_rename2.pl · GitHub

diff

スッキリ&直感的!になりました。

動いてるところ。

f:id:note103:20160518141012g:plain

まずバッチファイルであるところの conv.txt が数値順になってることを示した後、処理をやってます。
いい感じです!

なお、 glob, sort, grep の具体的な使い方で詰まった際、こちらもいつもお世話になっています木本裕紀さんのサイトにて、理解を補いました。

Perlに関するさまざまな情報が広く&深く&わかりやすく説明されていて、大変ありがたいです。

*1:4行目の say いらなかった。