I'm trying to mass rename the files from one extension to another (background: use haml instead of erb in my rails app). I get following output when issuing rename command:

% zmv '**/*.erb' $1.haml    
zmv: error(s) in substitution:
app/views/l/links/index.html.erb and app/views/index/index.html.erb both map to .haml
app/views/l/links/new.html.erb and app/views/l/links/index.html.erb both map to .haml
app/views/l/links/show.html.erb and app/views/l/links/new.html.erb both map to .haml
app/views/l/links/stats.html.erb and app/views/l/links/show.html.erb both map to .haml
app/views/layouts/application.html.erb and app/views/l/links/stats.html.erb both map to .haml
app/views/u/profiles/_form.erb and app/views/layouts/application.html.erb both map to .haml
app/views/u/profiles/edit.html.erb and app/views/u/profiles/_form.erb both map to .haml
app/views/u/profiles/show.html.erb and app/views/u/profiles/edit.html.erb both map to .haml
app/views/u/user_sessions/new.html.erb and app/views/u/profiles/show.html.erb both map to .haml
app/views/u/users/_form.erb and app/views/u/user_sessions/new.html.erb both map to .haml
app/views/u/users/new.html.erb and app/views/u/users/_form.erb both map to .haml
app/views/u/users/show.html.erb and app/views/u/users/new.html.erb both map to .haml

Can anyone point me to the right direction of fixing this?

  • do you want an answer to, how do I do this with zmv? or an answer to, how can mass rename files from one extension to another? Aug 22, 2010 at 8:12
  • @xenoterracide - yes ,)
    – Eimantas
    Aug 23, 2010 at 16:11

2 Answers 2


I think what you really want is something like this:

% zmv '(**/)(*).erb' '$1/$2.haml'
#      ^$1  ^$2

You need to use the parenthesis to create match groups, and you create a match group for the path to the file, and then a match group for the file name. Also, you need to make sure the second argument is also in single-quotes for zmv.

Also, it's a really good idea to test zmv commands with '-n' before you run them (-n will tell you what will be renamed, but not actually rename anything.)

  • Unfortunately (at least up to zsh 4.3.10), (**)/ matches exactly one directory level, i.e., ** loses its special meaning and is equivalent to *. So this command works but doesn't have the intended meaning. Aug 22, 2010 at 10:16
  • @Gilles: Yeah, I had it right the first time, and then I edited it because I'm stupid. However, (**/) works properly, so I'm going to update the post to what I had originally so that it's correct.
    – clee
    Aug 22, 2010 at 11:16

You need to tell zsh what $1 refers to. There are two possibilities:

  • Use parentheses around parts of the source pattern that you want to use. For example, in zmv '(*)/(*).erb' '$1/$2.haml', '$1' means what is matched by the first * and $2 means what is matched by the second $2.

    [EDIT (thanks to clee for pointing out that (**/) works)] Parentheses are a little awkward to use with multiple directory levels. If you write (**), the double asterisk loses its special meaning (only a single directory level is matched). And you mostly can't use / inside parentheses, so (**/*) is not a valid pattern. However, the special case (**/) works, so you can write zmv '(**/)(*).erb' '$1$2.haml'.

  • Use the -w option, in which case each $N corresponds to the text matched by the Nth wildcard in the source pattern. For example, zmv -w '**/*.erb' '$1/$2.haml' does what you want.

Note that you must always use single quotes around the replacement text (or use \$), otherwise the $s are expanded by the shell before they get to the zmv built-in command.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .