もた日記

くだらないことを真面目にやる

Zshメモ : zmvコマンドでリネーム(連番、ゼロ埋め、置換、拡張子、大文字、小文字など)

zmvの使い方


Zshzmvというコマンドを使うと、いろいろなリネームが簡単にできるので便利。
zmvコマンドを使うためにはautoloadが必要なので、下記行を.zshrcに追加しておく。

autoload -U zmv

簡単な使い方はzmvとコマンドを打ってリターンすると確認できる。マニュアルについてはman zshcontribの中に書いてある。

$ zmv
Usage:
  zmv [OPTIONS] oldpattern newpattern
where oldpattern contains parenthesis surrounding patterns which will
be replaced in turn by $1, $2, ... in newpattern.  For example,
  zmv '(*).lis' '$1.txt'
renames 'foo.lis' to 'foo.txt', 'my.old.stuff.lis' to 'my.old.stuff.txt',
and so on.  Something simpler (for basic commands) is the -W option:
  zmv -W '*.lis' '*.txt'
This does the same thing as the first command, but with automatic conversion
of the wildcards into the appropriate syntax.  If you combine this with
noglob, you don't even need to quote the arguments.  For example,
  alias mmv='noglob zmv -W'
  mmv *.c.orig orig/*.c

例に書いてあるように

$ zmv -W '*.lis' '*.txt'

を実行すると拡張子を.lisから.txtに変更できる(このくらいならrenameコマンドでもできるが)。

zmvのオプション


マニュアルには下記オプションの説明が書いてあるが、よく使いそうなものを見てみる。

zmv [ -finqQsvwW ] [ -C | -L | -M | -{p|P} program ] [ -o optstring ]


-n : 実行せずに変更内容を確認する

-nオプションはNo excecutionという意味で、変更内容は表示するが実際には実行しない。操作に慣れないうちはこのオプションで確認してから実行するのがよさそう。

$ zmv -nW '*.txt' '*.csv'
mv -- a.txt a.csv
mv -- b.txt b.csv


-W : ワイルドカードの扱いを簡単にする

通常の使い方では対象とするパターンのワイルドカード部分を()で囲む必要があり、()で囲んだものを参照する場合は順番に$1$2とする。下記例の場合、$1には-より前のfile$2には-より後のaまたはbが入る。

$ zmv -n '(*)-(*).txt' '$1_$2.csv'
mv -- file-a.txt file_a.csv
mv -- file-b.txt file_b.csv

-Wオプションを指定すると変換元(左側)のワイルドカードには括弧がついているとみなされ、変換先(右側)のワイルドカードは順番に$1$2とみなされるので、上記と同じ操作が以下のように簡単に書ける。

$ zmv -nW '*-*.txt' '*_*.csv'
mv -- file-a.txt file_a.csv
mv -- file-b.txt file_b.csv

なお、noglobを指定すればシングルクォーテーションも不要になりさらに簡単に書ける。

$ noglob zmv -nW *-*.txt *_*.csv
mv -- file-a.txt file_a.csv
mv -- file-b.txt file_b.csv

毎回このオプションを指定するのも大変なのでエイリアスを設定しておくとよい。

$ alias mmv='noglob zmv -W'
$ mmv -n *-*.txt *_*.csv
mv -- file-a.txt file_a.csv
mv -- file-b.txt file_b.csv


-f : ファイルが存在しても上書きする

リネーム先のファイルが存在する場合はエラーになるが、

$ zmv -nW '*.txt' '*.csv'
zmv: error(s) in substitution:
file exists: a.csv

-fオプションを指定すれば上書きできるようになる。

$ zmv -nWf '*.txt' '*.csv'
mv -- a.txt a.csv
mv -- b.txt b.csv


-C : コピーする

zmvという名前の通り、デフォルトではmvの操作をするが、cpしたい場合はCオプションを指定する。

$ zmv -nWC '*.txt' '*.csv'
cp -- a.txt a.csv
cp -- b.txt b.csv


-Ls : シンボリックリンクを作成する

シンボリックリンクを作成する場合は-Lsを指定する。-Llnコマンドを実行するということなので、-Lだけだとハードリンクになる。

$ zmv -WLs '*.txt' '*.csv'
$ ls -l
合計 0
lrwxrwxrwx 1 vagrant vagrant  5  2月 25 21:00 a.csv -> a.txt
-rw-r--r-- 1 vagrant vagrant  0  2月 25 20:55 a.txt
lrwxrwxrwx 1 vagrant vagrant  5  2月 25 21:00 b.csv -> b.txt
-rw-r--r-- 1 vagrant vagrant  0  2月 25 20:55 b.txt

以下のようにdotfilesにシンボリックリンクを作成する場合に使えそう。

$ zmv -WLs 'username/dotfiles/dot.*' '.*'


zmvを使ったリネームのパターン


zmvを使ったリネームのパターンをZSH-LOVERSなどを参考に調べてみる。リネームの動作がわかるように-nオプションを指定しているが、実際にリネームする場合は-nは不要。

拡張子を変更する(a.txt -> a.csv

-Wオプションの説明で書いた通り。

$ zmv -nW '*.txt' '*.csv'
mv -- a.txt a.csv
mv -- b.txt b.csv


拡張子を除去する(a.txt -> a)

変換先の拡張子を書かなければよい。

$ zmv -nW '*.txt' '*'
mv -- a.txt a
mv -- b.txt b


先頭に文字を追加する(a.txt -> file_a.txt)

変換先の先頭に文字を追加すればよい。

$ zmv -nW '*.txt' 'file_*.txt'
mv -- a.txt file_a.txt
mv -- b.txt file_b.txt


大文字化、小文字化する(AAA.txt -> aaa.txt)

大文字化、小文字化する場合はULといった変数展開フラグを使用する。

$ zmv -n '(*)' '${(U)1}'
mv -- AAA.txt AAA.TXT
mv -- BbB.txt BBB.TXT
mv -- CCc.TXT CCC.TXT
$ zmv -n '(*)' '${(L)1}'
mv -- AAA.txt aaa.txt
mv -- BbB.txt bbb.txt
mv -- CCc.TXT ccc.txt


先頭だけ大文字化する(file.txt -> File.txt)

先頭だけ大文字化したい場合はCを使用すればよい。

$ zmv -n '(*).txt' '${(C)1}.txt'
mv -- file.txt File.txt


連番を付与する(a.txt, b.txt -> a-1.txt, b-2.txt)

連番を付与する場合は以下のようにする。

$ c=1 zmv -n '*.txt' '$((c++)).txt'
mv -- a.txt 1.txt
mv -- b.txt 2.txt
mv -- c.txt 3.txt

初期値と加算値も変更可能。

$ c=3 zmv -n '*.txt' '$((c=c+2)).txt'
mv -- a.txt 5.txt
mv -- b.txt 7.txt
mv -- c.txt 9.txt

元のファイル名を残す場合。

$ c=1 zmv -n '(*).txt' '$1-$((c++)).txt'
mv -- a.txt a-1.txt
mv -- b.txt b-2.txt
mv -- c.txt c-3.txt


ファイル名に数値を加算する(pic1.jpg -> pic2.jpg)

連番の場合は数値の大きい方からリネームする必要があるので(On)によりファイル名の降順でソートする。(On)などのGlob Qualifiersを使う場合は-Qの指定が必要。また、ファイル名が連番の場合は-fが必要となる。

$ zmv -fQn 'pic([0-9]##).jpg(On)' 'pic$(($1 + 1)).jpg'
mv -- pic3.jpg pic4.jpg
mv -- pic2.jpg pic3.jpg
mv -- pic1.jpg pic2.jpg

(On)を指定しないと若い番号からリネームし、pic4.jpgだけが残ることになるので注意。

$ zmv -fQn 'pic([0-9]##).jpg' 'pic$(($1 + 1)).jpg'
mv -- pic1.jpg pic2.jpg
mv -- pic2.jpg pic3.jpg
mv -- pic3.jpg pic4.jpg


ゼロ埋めする(pic1.jpg -> pic0001.jpg)

数値をゼロ埋めする場合。桁数や埋める文字も変更可能。

$ zmv -n 'pic(*).jpg' 'pic${(l:4::0:)1}.jpg'
mv -- pic1.jpg pic0001.jpg
mv -- pic2.jpg pic0002.jpg
mv -- pic3.jpg pic0003.jpg
$ zmv -n 'pic(*).jpg' 'pic${(l:7::_:)1}.jpg'
mv -- pic1.jpg pic______1.jpg
mv -- pic2.jpg pic______2.jpg
mv -- pic3.jpg pic______3.jpg


ゼロ埋めした連番を付与する(a.txt, b.txt -> a-0001.txt, b-0002.txt)

連番とゼロ埋めの組み合わせ。

c=1 zmv -n '(*).txt' '$1-${(l:4::0:)$((c++))}.txt'
mv -- a.txt a-0001.txt
mv -- b.txt b-0002.txt
mv -- c.txt c-0003.txt


ゼロを除去する(pic0001.jpg -> pic1.jpg)

セロを除去したい場合は以下のようにする。##は最長マッチ。

$ zmv -n '(pic)0##(?*)' '$1$2'
mv -- pic0001.jpg pic1.jpg
mv -- pic0002.jpg pic2.jpg
mv -- pic0003.jpg pic3.jpg
mv -- pic0010.jpg pic10.jpg
mv -- pic0100.jpg pic100.jpg


数値の範囲を指定してリネームする

数値の範囲を指定したい場合は<1->のように指定する。<1->は1以上の場合。

$ zmv -n 'pic(<1->).jpg' 'pic${(l:4::0:)1}.jpg'
mv -- pic1.jpg pic0001.jpg
mv -- pic2.jpg pic0002.jpg
mv -- pic3.jpg pic0003.jpg
mv -- pic4.jpg pic0004.jpg
mv -- pic5.jpg pic0005.jpg

2以下の場合。

$ zmv -n 'pic(<-2>).jpg' 'pic${(l:4::0:)1}.jpg'
mv -- pic1.jpg pic0001.jpg
mv -- pic2.jpg pic0002.jpg

3以上4以下の場合。

$ zmv -n 'pic(<3-4>).jpg' 'pic${(l:4::0:)1}.jpg'
mv -- pic3.jpg pic0003.jpg
mv -- pic4.jpg pic0004.jpg


ファイル名のスペースを置換する(a a.txt -> a_a.txt)

置換する場合は${1//パターン/置換後文字列}を使用する。

$ zmv -n '(*)' '${1// /_}'
mv -- 'a a.txt' a_a.txt
mv -- 'b  b.txt' b__b.txt
mv -- 'c c  c.txt' c_c__c.txt

複数のスペースを1つにまとめたい場合は##で最長マッチにする。

$ zmv -n '(*)' '${1// ##/_}'
mv -- 'a a.txt' a_a.txt
mv -- 'b  b.txt' b_b.txt
mv -- 'c c  c.txt' c_c_c.txt


特定の文字を置換する(pic1.jpg -> file1.jpg)

スペースを置換する方法と同じ。

$ zmv -n '(*)' '${1//pic/file}'
mv -- pic1.jpg file1.jpg
mv -- pic2pic.jpg file2file.jpg


特定の文字を除去する(pic1.jpg -> 1.jpg)

置換後文字列をなくせば除去される。

$ zmv -n '(*)' '${1//pic/}'
mv -- pic1.jpg 1.jpg
mv -- pic2pic.jpg 2.jpg


最初の数文字を取り除く(0001_a.txt -> a.txt)

ファイル名の最初の4文字を取り除く場合は$f[5,-1]とする。

$ zmv -n '*' '$f[5,-1]'
mv -- 001_a.txt a.txt
mv -- 002_b.txt b.txt
mv -- 003_c.txt c.txt


文字列を入れ替える(file-1.txt -> 1-file.txt)

$1$2を入れ替えるなどすればよい。このような入れ替えの場合は-Wオプションは使えない。

$ zmv -n '(*)-(*).txt' '$2-$1.txt'
mv -- file-1.txt 1-file.txt
mv -- file-2.txt 2-file.txt


サブディレクトリも処理する

サブディレクトリも再帰的に処理したい場合は以下のようにする。$1にはフルパスが入る。

$ zmv -n '(**/)(*).txt' '$1$2.csv'
mv -- dir1/dir1-1/b.txt dir1/dir1-1/b.csv
mv -- dir1/dir1-2/c.txt dir1/dir1-2/c.csv
mv -- dir1/a.txt dir1/a.csv
mv -- dir2/dir2-1/e.txt dir2/dir2-1/e.csv
mv -- dir2/d.txt dir2/d.csv

簡潔に書くことも可能。

$ noglob zmv -nW **/*.txt **/*.csv
mv -- dir1/dir1-1/b.txt dir1/dir1-1/b.csv
mv -- dir1/dir1-2/c.txt dir1/dir1-2/c.csv
mv -- dir1/a.txt dir1/a.csv
mv -- dir2/dir2-1/e.txt dir2/dir2-1/e.csv
mv -- dir2/d.txt dir2/d.csv