OS 問わず Bash で一括リネームする
Bash でファイル名を一括でリネームしようと思い、調べ始めたらなかなか混乱していたので、整理する。
目次
- Windows Git SDK、Ubuntu Linux、MacOS で同じコマンドを使いたい
rename
コマンドは OS 間で全く違うので使わないfind
とsed
とxargs mv
でリネームするfind
で処理対象を絞り込むsed
でリネーム後の文字列を作るxargs mv
で実際に置換する- 以上
Windows Git SDK、Ubuntu Linux、MacOS で同じコマンドを使いたい
ファイルの一括リネームを実現するにあたって、OS の差異があるのは鬱陶しいので、どの環境でも同じコマンドでリネームできる方法を探すことにする。
Windows 環境では、自分は Git For Windows (GitBash) の上位互換である Git SDK を使っているのでその環境を対象にし、また、WSL Ubuntu でも同様に動くようにしたい。
同じく MacOS でも同じコマンドが動くようにしたい。GNU 系のコマンドを別途 Homebrew でインストールして利用する前提とする。
rename
コマンドは OS 間で全く違うので使わない
「ファイルの一括リネーム」を実現する、rename
というドンズバなコマンドが存在する。しかしこのコマンド、よくある名前なので混沌としていて、OS (導入環境) によって全く別のコマンドがインストールされる。以下、--help
オプションの出力内容を比較してみよう。
- Git SDK に内蔵の
rename
- util-linux というパッケージ群の一部らしい
- Git SDK 内蔵なのでファイルとしては
rename.exe
でありソースは読めない
$ rename --help
使い方:
rename [options] <expression> <replacement> <file>...
ファイル名を変更します。
オプション:
-v, --verbose explain what is being done
-s, --symlink act on the target of symlinks
-n, --no-act do not make any changes
-o, --no-overwrite don't overwrite existing files
-i, --interactive prompt before overwrite
-h, --help このヘルプを表示します
-V, --version バージョンを表示します
詳しくは rename(1) をお読みください。
$ rename --version
rename from util-linux 2.35.2
- Ubuntu 環境で
apt
からインストールできるrename
/usr/bin/rename
を参照すると、実体は Perl 製であることが分かる
$ apt install rename
$ rename --help
Usage:
rename [ -h|-m|-V ] [ -v ] [ -0 ] [ -n ] [ -f ] [ -d ]
[ -e|-E perlexpr]*|perlexpr [ files ]
$ rename -V
/usr/bin/rename using File::Rename version 1.10
- MacOS 環境で
brew
からインストールできるrename
/usr/local/bin/rename
を覗くと Perl 製であることが分かるが、Ubuntu Linux 版とは全く違う
$ brew install rename
$ rename --help
Usage:
rename [switches|transforms] [files]
Switches:
--man (read the full manual)
-0/--null (when reading from STDIN)
-f/--force or -i/--interactive (proceed or prompt when overwriting)
-g/--glob (expand "*" etc. in filenames, useful in Windows™ CMD.EXE)
-k/--backwards/--reverse-order
-l/--symlink or -L/--hardlink
-M/--use=Module
-n/--just-print/--dry-run
-N/--counter-format
-p/--mkpath/--make-dirs
--stdin/--no-stdin
-t/--sort-time
-T/--transcode=encoding
-v/--verbose
Transforms, applied sequentially:
-a/--append=str
-A/--prepend=str
-c/--lower-case
-C/--upper-case
-d/--delete=str
-D/--delete-all=str
-e/--expr=code
-P/--pipe=cmd
-s/--subst from to
-S/--subst-all from to
-x/--remove-extension
-X/--keep-extension
-z/--sanitize
--camelcase --urlesc --nows --rews --noctrl --nometa --trim (see manual)
# バージョン表示のオプションはないようなので man を抜粋する
$ rename --man
NAME
rename - renames multiple files
VERSION
version 1.601
…それぞれ全くオプションが違うので、各環境で使い方を覚えないといけない。Mac の rename
なんかはまぁまぁ使いやすいし、find -exec rename
といった組み合わせ方もあるのだが、いずれも他の環境で使えないので今回はコレ以上深堀りしない。
find
と sed
と xargs mv
でリネームする
そこで調べ直すと、
find
でリネーム対象を絞り込み、sed
でリネーム後のファイル名を組み立て、- 最後に
xargs mv
で実際にリネームする
という方法が、どの環境でも同じように処理できそうだと思った。
なお、MacOS の場合は find
・sed
ともに BSD 版であるため、細かなオプションの差異を吸収するために GNU 版を入れておくと良い。
$ brew install findutils gnu-sed
$ alias find='gfind'
$ alias sed='gsed'
find
で処理対象を絞り込む
まずは find
コマンドによる絞り込みの方法を押さえておく。
# カレントディレクトリ直下のファイルを全取得する
$ find . -type f -maxdepth 1
※ -maxdepth
オプションは、BSD 版だと -depth
・-d
オプションとも書けるが、GNU 版は -maxdepth
と書かないと有効にならない。
コレをベースに、-name
オプションなんかを使って絞り込んでやると良いだろう。
# 前方一致
$ find . -type f -maxdepth 1 -name 'prefix*'
# 部分一致
$ find . -type f -maxdepth 1 -name '*file-name*'
# 後方一致
$ find . -type f -maxdepth 1 -name '*-suffix.txt'
…こんな感じで、アスタリスク *
を使うと絞り込みやすい。
$ find . -type f -maxdepth 1 -name 'prefix*'
./prefix-001.jpg
./prefix-002.jpg
こんな風に、リネーム対象とするファイル名が列挙できた。
sed
でリネーム後の文字列を作る
続いて、リネーム後のファイル名文字列を sed
で作る。p
スクリプトコマンドを使うと、「処理前・(改行)・処理後」の順で出力できるので、コレを使う。
$ find . -type f -maxdepth 1 -name 'prefix-*' | sed 'p;s/prefix-/new-/'
./prefix-001.jpg
./new-001.jpg
./prefix-002.jpg
./new-002.jpg
正規表現を使いたい場合は -r
オプションを使う。普通の正規表現と違って、キャプチャした文字列を参照する場合は $1
・$2
(ドルマーク) ではなく \1
・\2
とバックスラッシュで書く。
# キャプチャを使って、ハイフン部分だけアンダースコアに変更する
$ find . -type f -maxdepth 1 -name 'prefix-*' | sed -r 'p;s/(prefix)-([0-9]*).jpg/\1__\2.jpeg/'
./prefix-001.jpg
./prefix__001.jpeg
./prefix-002.jpg
./prefix__002.jpeg
大文字・小文字変換は \L
や \U
で行える。このオプションは BSD sed では使えないので、GNU 版 gsed
を使おう。
# 小文字にする
$ find . -type f -maxdepth 1 -name 'prefix-*' | sed 'p;s/\(.*\)/\L\1/'
# 大文字にする
$ find . -type f -maxdepth 1 -name 'prefix-*' | sed 'p;s/\(.*\)/\U\1/'
./prefix-001.jpg
./PREFIX-001.JPG
./prefix-002.jpg
./PREFIX-002.JPG
ココまでで、リネームしたいファイル名とリネーム後の文字列が交互に列挙されることになる。ココまでで止めておくと、Dry-Run 的に置換結果を確認だけできる。
xargs mv
で実際に置換する
最後に、xargs -n2 mv
を繋げれば一括リネームが実行できる。
# 全ファイルの「BEFORE」部分を「AFTER」に置換する
$ find . -type f -maxdepth 1 | sed 'p;s/BEFORE/AFTER/' | xargs -n2 mv
# 一部ファイルを絞り込み正規表現を使って置換する
$ find . -type f -maxdepth 1 -name 'prefix-*' | sed -r 'p;s/(prefix)-([0-9]*).jpg/\1__\2.jpeg/' | xargs -n2 mv
こんな感じ。
以上
find
・sed
・xargs mv
の組み合わせで、イイカンジに対象ファイルを絞り込み、置換文字列を組み立て、実行できた。3つのコマンドの基本的なオプションしか使っておらず、ほぼ定型的に実装できるので、オプションを沢山覚えて使いこなす必要がなくシンプルだ。Dry-Run したくなった時も、オプションだなんだと考えずに xargs mv
を外すだけで良いので、分かりやすいであろう。