Moving with regular expressions - `mvr`

Every couple of months, I want to mass find-and-replace filenames. I know there’s a number of apps out there, but I can’t say I do this often enough that I want to pay for the software. (Also, every time I use it, I figure “just on a couple of files, not worth spending money on” - I’m sure it is if you count all the times I need to do this.) So rather than pay for a GUI app, I made a command-line app to do this for me.

mvr is like mv, but with regular expression support. The format is pretty basic, and can apply to your favourite form of regular expressions. In my case, exposure to TextMate (and now Sublime Text) has made me prefer the $1 form of regular expression find-and-replace.

Some use cases:

1. Simple find-and-replace

mvr ‘foo’ 'bar’

You’ve seen this, it’s basic mv syntax. It will find the file foo and rename it to bar.

2. Prefix a file

1
mvr '.*' 'prefix-$0'

$0 will match the whole filename. In this case, it will just add a prefix to every file in the directory. Note that to match multiple characters we need to use .* and not the * of globbing yore. This is unfortunately one way in which mvr’s and mv’s syntaxes diverge - if I want to support relatively good regular expressions, it’s a sacrifice I have to make.

1
mvr '.*\.csv' 'CSV-$0'

This will prefix every file that has the .csv extension. Note that since we support regex, we need to escape periods, which is unfortunate given how often they turn up in file names.

3. Change an extension

1
mvr '(.*).csv' '$1.txt'

$1 matches the first “capture” from the regex. Much like other regular expressions, you can capture with parentheses.

4. Play with numbers

1
mvr 'file-(\d+)-1' 'file$(1)1'

If you need numbers hard up against your matches, you might be worried that the interpreter will parse ’$11’ as 'match number 11’ as opposed to 'match number 1 followed by the numeral 1’ - and you’d be right. Using the $() syntax you can specify a match next to numbers.

5. Add dollar signs

1
mvr 'dollars-(\d+)' '$$$1'

The $$ character string will be replaced with $.

Other expressions

All the heavy regexp lifting is done by ruby’s regular expression engine, so everything you can imagine - lookahead and behind, capture based on word boundaries, etc., all work as expected (sometimes with a little escaping, since we’re in the shell).

Flags

-v: Verbose. This will handily inform you of every change that is occuring - which is sometimes good.

-d: Dryrun. Informs you of the changes that would be made, but doesn’t carry through. Handy when you’re trialling a regex pair.

-c: Check. This will tell you what will get changed, and ask if you want to go ahead. If the find-and-replace is fine, you can just let it run. Otherwise, you can back out and alter your regex. This is what I use rather than -v and -d - it’s handy enough that I could probably ditch all flags but this one, and might even consider making it on by default.

Future directions

I was trying to work out what else this needed, but I don’t think it really needs much else. It works well as-is, and more complexity just means more things to remember. The only thing I can think of that would be remotely handy is transformations - $(1u), for example, would uppercase match number 1 before putting it into the filename. But I can’t think of a time I’ve ever wanted to use this, so until I need it, it’ll stay on the “non-essential” list.

Code

Code is available on github.