ファイル一括リネームの(実用的な)実装方法を考える。

要件

以前作ったmdpファイルの一覧表示ツールに、「表示順にファイル名をリネームする」機能を実装したい。

ぐちゃぐちゃな名前のファイル群を一括リネームするのは割と需要の高い処理だよね。だから、イチから自分で考えなくてもググれば解決策が出てくる…かというと、そうでもないんだ。

よくある例(1)

プログラム系のサイトで「一括リネーム」の方法として紹介されているのは、だいたい以下の手順。

  1. 対象フォルダ内に存在するファイル名を全て取得
    →.jpgとか、.txtとか、拡張子で絞り込んで取得する場合もある

  2. 取得したファイル名をひとつ読み込み

  3. 現在のファイル名→変更したいファイル名にリネーム

  4. 2、3を繰り返す

問題点

シンプルな手順だしこれでいいじゃない、と思うかもしれないけど、これではまだ「実用的」じゃない。たとえば、

  • フォルダA

    • a.jpg

    • b.jpg

    • c.jpg

    • 001.jpg

    • 1.jpg

という5つのファイルを連番(001.jpg、002.jpg、…)にリネームしたいとしよう。すると、「a.jpg→001.jpg」の段階で、フォルダA/001.jpgは既に存在するという問題に直面する。
もしここで001.jpgを無理やり上書きすると、

  • a.jpg→001.jpg

  • b.jpg→002.jpg

  • c.jpg→003.jpg

  • 001.jpg→004.jpg(この時点の001.jpgは、既にa.jpgになっている)

  • 1.jpg→005.jpg

となり、最初に存在した001.jpgが消えてしまうんだ。

よくある例(2)

既存ファイルが消えるのを防ぐ方法として、「特殊な文字列を先頭につける」がある。hoge_001.jpg、hoge_002.jpg、…みたいな感じ。「hoge_」はどのファイルにも使われていない前提。
こうすれば、

  • a.jpg→hoge_001.jpg

  • b.jpg→hoge_002.jpg

  • c.jpg→hoge_003.jpg

  • 001.jpg→hoge_004.jpg

  • 1.jpg→hoge_005.jpg

これなら、確かにファイルの蒸発は起こらない。ただし、有効なのは最初の1度きりだ。

問題点

たとえば翌日、このフォルダに「d.jpg」という新しいファイルが追加されたとしよう。そして、

  • hoge_001.jpg

  • hoge_002.jpg

  • hoge_003.jpg

  • d.jpg

  • hoge_004.jpg

  • hoge_005.jpg

という位置にdを割り込ませて並び替えたいとする。すると、

  • hoge_001.jpg→hoge_001.jpg

  • hoge_002.jpg→hoge_002.jpg

  • hoge_003.jpg→hoge_003.jpg

  • d.jpg→hoge_004.jpg

  • hoge_004.jpg→hoge_005.jpg(この時点のhoge_004.jpgは、既にd.jpgになっている)

  • hoge_005.jpg→hoge_006.jpg(この時点のhoge_005.jpgは、既にd.jpgになっている)

となり、割り込んだ位置より後ろのファイルが全て消えてしまうんだ。
それに、「「hoge_」はどのファイルにも使われていない」という前提が成立しないファイル群に対しては、最初の1回目もうまく動作しない。

解決策

毎回同じ文字列を使うからファイルの蒸発が起こるんだから、毎回違う文字列を先頭につければいい。簡単なのは、現在時刻を使う方法。

1回目の時刻:yyyyMMddHHmmss(1)とする。より厳密にしたい場合はミリ秒まで取得すればいい。

  • a.jpg→yyyyMMddHHmmss(1)_001.jpg

  • b.jpg→yyyyMMddHHmmss(1)_002.jpg

  • c.jpg→yyyyMMddHHmmss(1)_003.jpg

  • 001.jpg→yyyyMMddHHmmss(1)_004.jpg

  • 1.jpg→yyyyMMddHHmmss(1)_005.jpg

ただ1回目の場合は、ファイル群の中にたまたまyyyyMMddHHmmss(1)_001.jpg~yyyyMMddHHmmss(1)_005.jpgと一致する名前のものが最初から存在するかもしれないよね(そんなことあるわけないやん、と思うかもしれないけど、そういうレアケースにも備えていないと実用的とは言えない)。だから、リネーム前にyyyyMMddHHmmss(1)_001.jpg~yyyyMMddHHmmss(1)_005.jpgが既に存在するか?をチェックする必要がある。
もし存在したら、現在時刻をもう一度取得しなおして先頭文字列にすればいい。ミリ秒まで使えば、完全一致する可能性はかなり低いし、何度も時刻を取得していればそのうちどのファイルにも存在しない時刻が取得できる。実行時間にも大した影響はないはず。

d.jpgというファイルが追加されてから実行する、2回目の時刻:yyyyMMddHHmmss(2)とする。

  • yyyyMMddHHmmss(1)_001.jpg→yyyyMMddHHmmss(2)_001.jpg

  • yyyyMMddHHmmss(1)_002.jpg→yyyyMMddHHmmss(2)_002.jpg

  • yyyyMMddHHmmss(1)_003.jpg→yyyyMMddHHmmss(2)_003.jpg

  • d.jpg→yyyyMMddHHmmss(2)_004.jpg

  • yyyyMMddHHmmss(1)_004.jpg→yyyyMMddHHmmss(2)_005.jpg

  • yyyyMMddHHmmss(1)_005.jpg→yyyyMMddHHmmss(2)_006.jpg

こうすれば、どんなファイル群に対しても、何回でも連番リネームが可能だ。

「現在時刻なんて、先頭にいらないんだけど…」っていう場合は、

  • ループ1周目

    • a.jpg→yyyyMMddHHmmss(1)_001.jpg

    • b.jpg→yyyyMMddHHmmss(1)_002.jpg

    • c.jpg→yyyyMMddHHmmss(1)_003.jpg

    • 001.jpg→yyyyMMddHHmmss(1)_004.jpg

    • 1.jpg→yyyyMMddHHmmss(1)_005.jpg

  • ループ2周目

    • yyyyMMddHHmmss(1)_001.jpg→001.jpg

    • yyyyMMddHHmmss(1)_002.jpg→002.jpg

    • yyyyMMddHHmmss(1)_003.jpg→003.jpg

    • yyyyMMddHHmmss(1)_004.jpg→004.jpg

    • yyyyMMddHHmmss(1)_005.jpg→005.jpg

みたいにループを2周して、「時刻を削除するリネーム」をしてやればいい。

この記事が気に入ったらサポートをしてみませんか?