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? – xenoterracide Aug 22 '10 at 8:12
  • @xenoterracide - yes ,) – Eimantas Aug 23 '10 at 16:11

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. – Gilles 'SO- stop being evil' Aug 22 '10 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 '10 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.