mmv: Batch File Renaming That Won't Eat Your Data

2026-06-30

Vladimir Lanin wrote mmv in 1989. Thirty-seven years later, developers are still writing this:

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

Or worse, this shudders:

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

Both work for the trivial case. Both fall apart the instant you need to swap, rotate, or merge filenames, and both will happily destroy data when the source set and destination set overlap. mmv was designed from day one to refuse to do that.

The Core Idea

mmv takes a source pattern with wildcards and a destination pattern with numbered backreferences. #1 refers to whatever the first wildcard matched, #2 the second, and so on:

$ mmv '*.jpeg' '#1.jpg'
$ mmv 'report_*_*.pdf' '#2_report_#1.pdf'
$ mmv 'IMG[0-9][0-9][0-9][0-9].JPG' 'photo_#1#2#3#4.jpg'

Character classes count as wildcards too, hence four backreferences for four digits. The patterns are shell-glob style, not regex — readable at 6 AM.

The Killer Feature: A Two-Phase Plan

Before touching a single inode, mmv computes the entire rename graph and checks for collisions, cycles, and lost data. If anything looks wrong, it aborts before doing anything:

$ ls
a.txt  b.txt  c.txt
$ mmv '*.txt' 'all.txt'
a.txt -> all.txt : collision.
mmv: nothing done.

Compare to the shell loop, which would have happily overwritten a.txt with b.txt and then again with c.txt, leaving you with one file and a sinking feeling.

It also handles swaps that would deadlock a naive renamer:

$ ls
foo.txt  bar.txt
$ mmv ';*.txt' '#1#2.tmp.txt' && mmv '*.tmp.txt' '#1.txt'   # the manual way
$ mmv -r 'foo.txt' 'bar.txt'                                  # mmv handles it

Internally it sorts the operations into safe order, using a temp name if it has to break a cycle. You don't think about it.

The Four Tools in One

mmv is actually shorthand for a family controlled by flags:

The append mode is the secret weapon: mmv -a 'log.*' 'all.log' concatenates every rotated log into one file, in glob order, no cat-and-redirect gymnastics. Combined with the collision check, you can't accidentally append a file into itself.

The Dry Run You Actually Trust

$ mmv -n '*.jpeg' '#1.jpg'
a.jpeg -> a.jpg
b.jpeg -> b.jpg
c.jpeg -> c.jpg

-n prints the full plan with zero side effects. Because the same planner runs in real mode, the dry run is exactly what will happen — not a best-guess simulation like the shell-loop equivalent.

Why Not Perl rename?

Perl's rename evaluates an arbitrary Perl expression per file. That's powerful and terrifying. It has no collision detection — rename 's/^\d+_//' * on a directory where two files would map to the same name silently clobbers one. mmv traded the expressiveness of regex substitution for safety guarantees, which is the right trade for the 95% of renames you actually do.

Install: apt install mmv, brew install mmv, or your distro's equivalent. It's 70 KB. It will outlive your shell history.

Key Takeaway: mmv renames files in bulk using glob patterns and backreferences, but its real value is the two-phase planner that detects collisions and cycles before touching a single byte.

All newsletters