Xeebi

home categories feeds

Bash で複数ファイルの拡張子を一括で変更

Bash で(も何でもいいが,とにかく手近なツールで)current directory にある複数ファイルの拡張子を一括で変更したい.どうするだろうか. ここでは細かい例外処理とか扱いづらいファイル名とかはだいたい無視して良いし,上書きも気にしない.

これまで

ずいぶん前の,bash に触れたての僕なら多分こうしていたと思う(本気で).以下 *.JPEG*.jpg に変えたいとしよう.

for f in `ls -1 *.JPEG`; do
  newfilename=`echo $f | sed -e "s/JPEG$/jpg/"`
  mv $f $newfilename
done

わりとやばい感じがする.まず Vim と仲良くなったので,次に僕が仲良くなろうとしたのは sed だったのだ. ついでにこのままだと空白の入ったファイル名で死ぬがきちんと quote して避けられるかどうか自信がない.

それから少し経って Python に馴染み,昨日までなら微妙にめんどくさがりつつ python で書いただろうとおもう. 特にもっと複雑なことをしたくなったときに無いなりに python 力を援用できるので.

#!/usr/bin/env python3
import os

from_ext = ".JPEG"
to_ext = ".jpg"

for f in os.listdir('.'):
    if f.endswith(from_ext):
        print(f)
        os.rename(f, f[:-len(from_ext)]+to_ext)

まあこんな感じ? 単純な rename にあんまり頭使うのも悔しい感じでこういうことになった. 色々対応する必要があるなら適宜 isfile とか使っていくといいんだと思う.

Ruby なら Dir.foreach('.'){|f|} とかやれば綺麗にあげぽよに書ける気がする.普段触らないから分からないが.

知見

まず rename というすごくそれらしい名前のコマンドの存在を知る.これの本体は perl のスクリプトで,システムによっては別のものを指してるかもなので注意だとかいうことだ.

$ man rename

rename [ -v ] [ -n ] [ -f ] perlexpr [ files ]

-v, --verbose はファイル名を出力, -n, --no-act は実際には rename しない, -f, --force は上書きするというオプション. これを使えば

$ rename 's/\.JPEG$/.jpg/' *.JPEG

で行けそうだ.

こちらはそんなに使い勝手はよくないのかもしれないが basename ってのがあって

$ mv "$file" "`basename $file .JPEG`.jpg"

みたいな感じで使うことが出来る.

そして革命

実は (Advanced Bash-Scripting Guide を読んでいて知ったんだが), bash なら変数内で文字列置換みたいなことをすることが出来る.

例えば

a="foobar"
echo ${a/foo/bar}
# => barbar
echo ${a//o/0}
# => f00bar

と言った具合で,これについては shとbashでの変数内の文字列置換など - ろば電子が詰まっている に詳しい.

これを使えば

for f in *.JPEG; do
  mv "$f" "${f%.JPEG}.jpg"
done

で行ける.${f%.JPEG} で後方最短マッチの削除,空白なども簡単に quote できていて,なかなかすっきりしていていい感じだ. たとえば 2014-07-01-my-post.md とかいう長ったらしいのを 07-01 から 07-02 にしたいみたいなときにも楽そうで life-changing.

Bash もかなり色々機能があって,そこでゴリゴリ頑張るか python とかに流れるかは難しいところだが,つい python や ruby で全部やってしまいがちなので もうちょっと bash で色々やるようにしたい.