Why Linux Renaming Feels Confusing (and How to Cut Through It)
The confusion comes from one word meaning two different programs. There are two entirely separate commands both named rename, and which one you have depends on your distribution. There is the Perl rename (often shipped as prename or rename on Debian and Ubuntu derivatives) that takes a Perl regular expression, and there is the util-linux rename (common on Fedora, CentOS, and many minimal installs) that takes three plain arguments and does a simple text substitution. They share a name and nothing else. On top of that pair sit two more capable tools, mmv for wildcard pattern renames and zmv for anyone working in the zsh shell, plus the universal fallback that always works: a for loop calling mv.
So the honest answer to how do I bulk rename files on Linux is a question back at you. Which rename do you have, and how complex is the mapping from old names to new ones? Answer those two things and the right tool falls out almost automatically. The rest of this reference walks each tool in the order you are most likely to reach for it, then gives you a table and a decision rule to lock the choice in.
The Perl rename: Regex Renames in One Line
The Perl version of rename is the most powerful of the built-in options because it accepts a full Perl substitution expression. The pattern is rename followed by a substitution and a file list. To replace every space in the current directory with an underscore you run rename 's/ /_/g' * where the s/ /_/g is a global substitution, space to underscore, applied to each matching filename. Because it is real regex you can do far more than swapping characters: rename 's/\.JPEG$/.jpg/' * lowercases and shortens the extension, and rename 'y/A-Z/a-z/' * transliterates every filename to lowercase in one pass.
The feature that makes it safe is the dry run. Add the -n flag and rename prints what it would do without renaming anything, so rename -n 's/ /_/g' * shows you a preview of every rename before you commit. Run that first, read the output, then run the same command without -n. Take a folder of camera exports where one file is IMG 2938.JPEG. Running rename -n 's/ /_/g; s/\.JPEG$/.jpg/' * would report IMG 2938.JPEG renamed as img... only after you also lowercase, so the full pipeline IMG 2938.JPEG turns into img_2938.jpg once your substitutions handle the space, the case, and the extension together. That is the Perl rename in a sentence: reach for it whenever the change is a regex.
The util-linux rename: The Simple Positional Swap
The other rename is far simpler and, on distributions that ship it, far more common. The util-linux rename takes three positional arguments: the text to find, the text to replace it with, and the files to act on. To turn every .jpeg into .jpg you run rename .jpeg .jpg *.jpeg. There is no regex here, just a literal from-text and to-text, which is either a limitation or a relief depending on the job. For a plain extension swap or a straight word replacement it is quicker to type and harder to get wrong than an escaped regex.
The catch is knowing which rename you are actually holding, because the syntax that works on one will fail or misbehave on the other. The quickest test is to run rename --version and read the first line: if it mentions util-linux you have the positional version, and if it mentions Perl or shows something like /usr/bin/rename you are on the Perl one. Another giveaway is behavior, if rename 's/a/b/' filename does nothing useful and just complains, you almost certainly have the util-linux build and need positional arguments instead. Check once per machine and you never have to wonder again.
mmv: Wildcard Pattern Mapping
When the rename is less a find-and-replace and more a structural remap of names, mmv is the tool built for exactly that. It is not always installed, so you may need to add it with your package manager (apt install mmv or dnf install mmv), but once it is there it handles wildcard-to-wildcard mappings cleanly. The idea is that wildcards in the source pattern are captured and referenced by position in the destination. Running mmv '*.jpeg' '#1.jpg' renames every .jpeg to .jpg, where the #1 stands for whatever the first wildcard matched. A file named holiday.jpeg becomes holiday.jpg, and vacation_02.jpeg becomes vacation_02.jpg, all in one command.
The power shows when you have multiple wildcards to shuffle. A pattern like mmv 'report_*_*.txt' 'report_#2_#1.txt' swaps two captured segments, which is awkward to express with either rename and tedious with a loop. mmv also refuses to clobber existing files by default and can report collisions before it acts, which makes it a comfortable choice when the mapping is intricate enough that you want a tool watching your back. Reach for mmv when the job is a complex wildcard mapping rather than a simple substitution.
zmv: The zsh Built-In
If your shell is zsh, you already have one of the best batch renamers on the system and may not know it. zmv is a zsh function you load once per session with autoload -U zmv, after which it renames using zsh glob patterns with parenthesized groups you reference as $1, $2, and so on. To back up every text file you run zmv '(*).txt' '$1.bak', where (*) captures the base name and $1 replays it with a new extension, so notes.txt becomes notes.bak. It reads a lot like the mmv idea but uses native zsh globbing, which means you get zsh's full pattern vocabulary, including recursive **/ matching, for free.
zmv also has a dry-run habit worth building: run it with -n to preview, as in zmv -n '(*).txt' '$1.bak', and it lists each rename it would perform without doing them. Because it is part of the shell rather than a separate binary, zmv is the natural pick for anyone who lives in zsh and does not want to install anything. The only prerequisite is remembering the autoload line, which many people simply drop into their .zshrc so zmv is always ready.
The for Loop: The Fallback That Always Works
When nothing is installed, when you are on a stripped-down container or a recovery shell, or when the logic is fiddly enough that you want plain visible steps, the for loop is the fallback that never lets you down. It works in bash, in sh, and in zsh, and it needs nothing beyond mv. A loop like for f in *.jpeg; do mv "$f" "${f%.jpeg}.jpg"; done walks every .jpeg file and renames it, using the shell's own ${f%.jpeg} parameter expansion to strip the old extension before appending the new one.
The loop trades brevity for control. You can insert an echo in front of the mv to preview, as in for f in *.jpeg; do echo mv "$f" "${f%.jpeg}.jpg"; done, which prints the commands without running them, your manual dry run. You can add conditionals, log each move, or build the new name from several pieces of the old one. It is more typing than a one-line rename, but it is portable to the point of being universal, and it is the answer whenever the environment is too minimal for the specialized tools or the naming logic is too custom for a single pattern.
Here is the rule that collapses all of the above into a single choice. If the change is a quick regex, a substitution, a case change, a pattern strip, reach for the Perl rename and preview it with rename -n. If you have no rename installed or the job is a simple literal swap like one extension for another, use the util-linux rename with its positional arguments, or drop to a for loop if even that is missing. If the mapping is a complex wildcard rearrangement with captured segments moving around, use mmv. And if you are working in zsh, use zmv, because it is already there and speaks the shell's own glob language. Two questions decide it: which rename you have, and how complex the old-to-new mapping is.
The table below lines up the four named tools across the traits that actually drive the choice, so you can match the job in front of you to the tool that fits it.
| Tool | Best for | Regex support | Dry run | Where it ships |
|---|
| Perl rename | Regex renames, case changes, substitutions | Yes, full Perl regex | Yes, rename -n | Debian, Ubuntu (often as rename or prename) |
| util-linux rename | Simple literal from-text to-text swaps | No, positional text only | Limited, no true preview flag | Fedora, CentOS, many minimal installs |
| mmv | Complex wildcard-to-wildcard mappings | Wildcards, not full regex | Reports collisions, use with care | Not default, install via apt or dnf |
| zmv | Pattern renames inside the zsh shell | zsh globs with capture groups | Yes, zmv -n | Built into zsh, autoload -U zmv |
A Note on Scanned Documents and OCR
One limit is worth stating plainly, because it trips people up. Every tool on this page renames by filename only. rename, mmv, zmv, and a for loop all operate on the string of characters that is the file's name; none of them can open a file, read its contents, and name it based on what is inside. If you have a folder of scanned PDFs called scan0047.pdf and scan0048.pdf and you want them named after the invoice number or the vendor printed on the page, no Linux rename command can do that. Reading a scanned document requires optical character recognition, which is a different job from string manipulation. The CLI tools here are the right answer for pattern-based renaming and the wrong tool entirely for content-based renaming.
Where Renamer.ai Fits (and Where It Does Not)
To be honest about the boundary: renamer.ai is a content-based renamer that reads what is inside a document, the vendor, the invoice number, the date, and names the file from that, but it runs on Windows and Mac, not as a Linux CLI tool. If you are on Linux and need to rename by pattern, the commands on this page are exactly what you want and renamer.ai is not the right fit for that machine. It solves the OCR problem described just above, not the pattern-renaming problem this reference is about. For the full landscape of pattern-based and content-based options across platforms, the bulk rename software hub lays out where each approach belongs.