The mv Loop Gets Old Fast
You’ve been there. Three files to rename — no problem, just mv them one at a time. But somewhere around file twelve, you realize you’re doing something a computer was literally built for, and you’re doing it manually like a cursed goblin.
# Fine for 3 files. Miserable for 300.mv old_report_1.txt new_report_1.txtmv old_report_2.txt new_report_2.txt# ... kill meLinux has several proper tools for bulk renaming. Which one to reach for depends on whether you want regex power, visual editing, or glob patterns. Here’s the breakdown.
rename — Regex Renaming (the Perl One)
The rename command on Debian/Ubuntu systems is Perl-based and takes a Perl substitution expression. It’s the heavy hitter for pattern-based renames.
# Install on Debian/Ubuntusudo apt install rename
# Basic usage: replace 'foo' with 'bar' in all .txt filenamesrename 's/foo/bar/g' *.txt
# Always dry-run first — see what would happen without doing itrename -n 's/foo/bar/g' *.txtCommon patterns you’ll actually use:
# Lowercase all filenamesrename 's/\(.*\)/\L$1/' *
# Replace spaces with underscoresrename 's/ /_/g' *
# Change extension from .jpeg to .jpgrename 's/\.jpeg$/.jpg/' *.jpeg
# Add a prefix to all filesrename 's/^/2024-/' *.txt
# Strip a prefixrename 's/^IMG_//' *.jpg
# Recursive rename (using find to feed it)find . -name "* *" -exec rename 's/ /_/g' {} +Wait, Which rename Do You Have?
Here’s the annoying part: there are two rename commands. The Perl version (above) and a simpler util-linux version on some systems. Check which you have:
rename --versionIf you see util-linux, the syntax is completely different — it takes a fixed string substitution, not regex: rename old new files. The Perl version will say something like rename from util-linux or show a version string from the rename Perl package.
On Fedora/RHEL, the Perl version is called prename. On Arch it’s available as perl-rename. Worth knowing before you copy-paste a regex from Stack Overflow and wonder why nothing happened.
vidir — Edit Filenames Like a Text File
This one is genuinely underrated. vidir (from the moreutils package) dumps all filenames into your $EDITOR, you edit them however you want, save and quit, and it renames everything accordingly.
sudo apt install moreutils
# Rename files in current directoryvidir
# Rename specific filesls *.jpg | vidir -
# Rename recursively (careful)find . -type f | vidir -What opens is something like:
1 ./IMG_001.jpg2 ./IMG_002.jpg3 ./IMG_003.jpgChange the names on the right side. Delete a line entirely to delete that file. Reorder lines — vidir handles it. Save, quit, done.
This is hands-down the best tool when you have a complex rename you want to think about. You can use all your editor tricks — multi-cursor, regex replace across the buffer, whatever. Nothing changes until you save. Close without saving and nothing happens at all.
Use this for: renaming a batch of photos after a trip, reorganizing project files, any rename where you want human eyes on each decision.
mmv — Glob-Based Mass Rename
mmv (mass move) uses shell glob patterns with numbered back-references. Less common but handy when you’re thinking in glob terms rather than regex.
sudo apt install mmv
# Rename all .txt files to .bakmmv '*.txt' '#1.bak'
# Add a prefixmmv '*.jpg' 'vacation_#1.jpg'
# Change double extensionmmv '*.tar.gz' '#1.tgz'The #1 refers to what matched the first *. It’s not as powerful as Perl regex but the syntax is more readable for simple transformations.
fd + -x — Find and Rename in One Shot
fd is the modern find replacement, and its -x flag executes a command per result with some nice placeholder variables.
sudo apt install fd-find# On some systems it's 'fdfind', alias it: alias fd=fdfind
# Change .jpeg to .jpg for all files recursivelyfd --extension jpeg -x mv {} {.}.jpg
# Remove spaces from filenames found recursivelyfd --regex ' ' -x bash -c 'mv "$1" "${1// /_}"' _ {}Placeholders: {} is the full path, {.} strips the extension, {/} is the filename only, {//} is the parent directory. The -x flag handles filenames with spaces safely — no quoting headaches.
zmv — zsh Users, You Have This Already
If you’re on zsh, zmv is built in (load it with autoload -U zmv). It uses zsh’s extended glob syntax.
autoload -U zmv
# Change extensionzmv '(*).txt' '$1.md'
# Add prefixzmv '(*).jpg' 'photo_$1.jpg'
# Dry-runzmv -n '(*).txt' '$1.md'It’s not installed separately — it’s just there waiting. Nice when you’re already in zsh and don’t want to install anything.
Practical Scenarios
Rename 500 Photos to a Consistent Format
You’ve got IMG_0001.jpg through IMG_0500.jpg and want 2024-01-0001.jpg etc:
rename 's/IMG_/2024-01-/' IMG_*.jpgFor more complex date-based renaming with exiftool:
# Rename by EXIF date — different tool but worth knowingexiftool '-FileName<DateTimeOriginal' -d '%Y-%m-%d_%H%M%S.%%e' *.jpgRemove Spaces from Filenames Recursively
Classic problem. Two approaches:
# Using find + rename (Perl version)find . -name "* *" -exec rename 's/ /_/g' {} +
# Using fdfd --regex ' ' -x bash -c 'mv "$1" "${1// /_}"' _ {}Change Extension for All Files in a Directory
# rename (Perl)rename 's/\.txt$/.md/' *.txt
# Pure bash (no extra tools needed)for f in *.txt; do mv "$f" "${f%.txt}.md"; doneComplex Rename You Want to Review
vidirOpen it, take your time, edit like a document, save when you’re confident.
Quick Reference
| Tool | Best For | Install |
|---|---|---|
rename (Perl) | Regex-based renames, extensions, prefixes | apt install rename |
vidir | Visual/manual review, complex batches | apt install moreutils |
mmv | Glob patterns with back-references | apt install mmv |
fd -x | Find + rename pipeline, recursive ops | apt install fd-find |
zmv | zsh users, already available | autoload -U zmv |
for loop | Simple, no deps, 5-10 files | Built-in bash |
The right tool depends on the job. Regex substitution across 500 files? rename. Batch of photos you want to manually review? vidir. Already in a fd pipeline? Use -x. Already in zsh? zmv is sitting right there.
Dry-run everything first. rename -n shows you what would happen. vidir shows you the changes before you save. mmv has a -n flag too. Your future self — the one who has to explain to a client why all their files are now named undefined — will thank you.