Safely Rename Groups of Files with rename.ul
Renaming fifty files by hand invites a typo: rename.ul previews the change first, so you can check every destination.
The route
Jump straight to the step you need, or tick off Done means at the end.
- 1. Check the installed command
- 2. Build a small test set
- 3. Preview the proposed names
- 4. Apply the rename with a guard
- 5. Replace all or only the last occurrence
- 6. Fix extensions without touching file contents
- 7. Handle collisions, links and paths deliberately
- 8. Read the result and recover from a failure
The examples keep the operation in a temporary directory and show how to preview, apply and verify a repeatable batch rename, such as replacing an old suffix or fixing a file extension. Allow about ten minutes if you already know the expression and replacement you need.
You need a shell and the util-linux package. On this machine, the installed command is rename.ul from util-linux 2.39.3, packaged as util-linux 2.39.3-9ubuntu6.6. The command normally needs no elevated privileges: use sudo only when the files and their containing directory genuinely require it, and do not test an unfamiliar rename as root.
1. Check the installed command
Confirm which executable is being used and read its version. These are ordinary, read-only checks:
$ command -v rename.ul
/usr/bin/rename.ul
$ rename.ul --version
rename.ul from util-linux 2.39.3
The command is also commonly documented as rename(1), but this util-linux implementation is distinct from Perl-based commands that may also be called rename. Use rename.ul in scripts when you specifically need this syntax and behaviour.
2. Build a small test set
Practise in a directory containing copies, not the only copy of valuable data. This setup creates empty files, so no real content is at risk:
$ mkdir -p /tmp/rename-demo
$ cd /tmp/rename-demo
$ touch report-old.txt report-old.csv notes.txt
$ ls -1
notes.txt
report-old.csv
report-old.txt
Keep the directory path visible in your prompt, or check it with pwd. A common distraction is running a correct command from the wrong directory. The file arguments are expanded by the shell before rename.ul sees them, so a wildcard that matches nothing may be passed through literally on some shells.
3. Preview the proposed names
The command takes an expression, a replacement, and one or more file names, and replaces the first occurrence of the expression in each selected name. Add --no-act and --verbose for a dry run:
$ rename.ul --no-act --verbose old new report-old.*
`report-old.csv' -> `report-new.csv'
`report-old.txt' -> `report-new.txt'
Nothing has changed at this checkpoint. The expression is old, the replacement is new, and report-old.* is expanded by the shell to the two matching files. Check every proposed destination before proceeding, especially if a wildcard selects more files than expected.
4. Apply the rename with a guard
For a first real operation, keep --verbose and add --no-overwrite, which refuses a destination that already exists:
$ rename.ul --no-overwrite --verbose old new report-old.*
`report-old.csv' -> `report-new.csv'
`report-old.txt' -> `report-new.txt'
$ ls -1
notes.txt
report-new.csv
report-new.txt
The default is less forgiving: without --no-overwrite, --interactive or --no-act, the command has no built-in safeguard against overwriting. Treat this as a destructive operation: back up or use version control before changing a valuable directory. If a rename has just been applied and the new names are known, undo it by swapping the same expression and replacement, for example rename.ul --no-overwrite new old report-new.*. Check the dry run first; an existing old-name file can make an automatic undo unsafe.
5. Replace all or only the last occurrence
The default changes only the first occurrence. Use --all when every occurrence should change:
$ touch image-old-old.jpg
$ rename.ul --no-act --verbose --all old new image-old-old.jpg
`image-old-old.jpg' -> `image-new-new.jpg'
Use --last when only the final occurrence matters:
$ touch archive-2025-old.tar
$ rename.ul --no-act --verbose --last old new archive-2025-old.tar
`archive-2025-old.tar' -> `archive-2025-new.tar'
--all and --last describe how the expression is replaced within each name; they do not select files. File selection still comes from the arguments and shell expansion.
6. Fix extensions without touching file contents
A typical use is correcting a suffix across a group of files:
$ rename.ul --no-act --verbose .htm .html -- *.htm
`index.htm' -> `index.html'
`help.htm' -> `help.html'
The -- separates options from file names, which matters when a file name begins with a hyphen. Rename changes directory entries, not the bytes inside the files. After applying the command, verify both the names and the count:
$ rename.ul --no-overwrite --verbose .htm .html -- *.htm
$ find . -maxdepth 1 -type f -name '*.html' -print | sort
./help.html
./index.html
7. Handle collisions, links and paths deliberately
With --interactive, the command asks before overwriting an existing destination; in a normal terminal, press Enter after your answer. This is still a destructive choice, so inspect the proposed destination rather than accepting prompts mechanically. A non-interactive job should prefer --no-overwrite and check its exit status.
By default, a symlink itself is renamed. With --symlink, the command changes where the symlink points instead, which changes link state and can redirect programs that use the link. Preview it and inspect the link with readlink before applying it:
$ readlink current-report
report-old.txt
$ rename.ul --no-act --verbose --symlink old new current-report
`current-report' -> `current-report'
If either the expression or replacement contains a slash, the full path is updated rather than only the final name component, which can move a file between directories. The command does not create missing directories or support moving between filesystems. Leave slashes out unless moving is specifically intended, and use mv when a clear, separately reviewed move is easier to understand.
8. Read the result and recover from a failure
Exit status 0 means every requested rename succeeded. Status 1 means all failed, status 2 means some failed, status 4 means nothing was renamed, and status 64 means an unexpected error occurred. Capture it immediately if a script needs to tell these cases apart:
$ rename.ul --no-overwrite --verbose old new report-old.*
$ status=$?
$ printf 'rename.ul status: %s\n' "$status"
rename.ul status: 0
If a destination already exists, stop and compare the two files before choosing whether to rename, back up or remove anything; do not use rm as an automatic collision fix. If a command was interrupted, list the directory and use the verbose output to identify which operations completed, then make a new dry run for the remaining names. There is no transaction that rolls a multi-file operation back as one unit.
Done means
- Version checked: you confirmed the util-linux version and used the intended
rename.ulbinary. - Dry run shown: a dry run showed the exact source and destination names.
- Guard used: you used
--no-overwrite,--interactiveor--no-actwhere appropriate. - Scope checked: you checked whether first, last or all occurrences should change.
- Higher-risk cases flagged: symlink targets and paths containing slashes were treated as separate, higher-risk cases.
- Verified afterwards: you checked the directory afterwards and have a reviewed recovery path.