Perl rename command
This chapter will show a few examples for renaming files using the rename
command. There are several implementations for this particular command. So, check man rename
to see if you get the Perl based rename
documentation as shown below:
NAME
rename - renames multiple files
SYNOPSIS
rename [ -h|-m|-V ] [ -v ] [ -0 ] [ -n ] [ -f ] [ -d ]
[ -e|-E perlexpr]*|perlexpr [ files ]
DESCRIPTION
"rename" renames the filenames supplied according to the rule
specified as the first argument. The perlexpr argument is a
Perl expression which is expected to modify the $_ string in
Perl for at least some of the filenames specified. If a given
filename is not modified by the expression, it will not be
renamed. If no filenames are given on the command line,
filenames will be read via standard input.
If you don't have the command installed, check your distribution's repository or you can install it from metacpan: File::Rename. Here are couple of implementations on my system:
$ rename --version
/usr/bin/rename using File::Rename version 1.10
$ rename.ul --version
rename.ul from util-linux 2.34
See also: askubuntu: What's the difference between the different "rename" commands?
See also F2: a cross-platform tool for batch renaming files and directories quickly and safely.
Basic example and sanity check
For this chapter, use an empty folder to follow along the examples presented. Delete the created files before moving on to the next illustration.
The below example formats the filenames to consistently have three digits, so that the sorted filename display works for the numbers as well. The -n
option allows you to do a sanity check without renaming the files.
$ touch 1.png 3.png 25.png 100.png
$ ls
100.png 1.png 25.png 3.png
# sanity check
# note that 100.png isn't part of the output, since it isn't affected
$ rename -n 's/\d+/sprintf "%03d", $&/e' *.png
rename(1.png, 001.png)
rename(25.png, 025.png)
rename(3.png, 003.png)
# remove the -n option after sanity check to actually rename the files
$ rename 's/\d+/sprintf "%03d", $&/e' *.png
$ ls
001.png 003.png 025.png 100.png
$ rm *.png
For a more pleasing visual of the sanity check, pipe the output to
column -ts,
as shown below (assuming filenames don't have comma in them).$ rename -n 's/\d+/sprintf "%03d", $&/e' *.png | column -ts, rename(1.png 001.png) rename(25.png 025.png) rename(3.png 003.png)
Verbose mode
The -v
option shows how the files have been renamed, similar to the -n
option. The difference is that the -v
option shows the result after the files have been renamed.
$ touch a.b.c.d.txt 1.2.3.txt
# replace all dot characters except the extension
# sanity check
$ rename -n 's/\.(?=.*\.)/_/g' *.txt
rename(1.2.3.txt, 1_2_3.txt)
rename(a.b.c.d.txt, a_b_c_d.txt)
# verbose mode
$ rename -v 's/\.(?=.*\.)/_/g' *.txt
1.2.3.txt renamed as 1_2_3.txt
a.b.c.d.txt renamed as a_b_c_d.txt
$ ls
1_2_3.txt a_b_c_d.txt
$ rm *.txt
File already exists
If a renaming operation matches a filename that already exists, such a renaming won't go through by default. You can override this behavior by using the -f
option.
$ touch report_v1.log report_v2.log
$ rename 's/v1/v2/' report_v1.log
report_v1.log not renamed: report_v2.log already exists
$ ls
report_v1.log report_v2.log
$ rename -f 's/v1/v2/' report_v1.log
$ ls
report_v2.log
$ rm *.log
Rename only the filename component
If you are passing filenames with path components in them, use the -d
option to affect only filename portion. Otherwise, the logic you are using might affect directory names as well.
$ mkdir scripts
$ touch scripts/{toc.sh,reports.py}
# uppercase the first character of the filename
$ rename -n -d 's/./\u$&/' scripts/*
rename(scripts/reports.py, scripts/Reports.py)
rename(scripts/toc.sh, scripts/Toc.sh)
# without the -d option, directory name is affected
$ rename -n 's/./\u$&/' scripts/*
rename(scripts/reports.py, Scripts/reports.py)
rename(scripts/toc.sh, Scripts/toc.sh)
$ rm -r scripts
Incrementing numbers
Unlike the normal Perl one-liners, the rename
command allows only strict
mode. So, you'll have to declare variables before using them. However, you can cheat a little by using the $a
and $b
global variables (see stackoverflow: Where do the $a and $b variables come from? for details).
The below example replaces the first occurrence of numbers in the filename with an incrementing sequence.
$ touch 1.png 3.png 25.png 100.png
$ rename -n 's/\d+/sprintf "%03d", ++$a/e' *.png
rename(100.png, 001.png)
rename(1.png, 002.png)
rename(25.png, 003.png)
rename(3.png, 004.png)
$ rm *.png
However, the above approach can lead to issues if a number already exists. You cannot use the -f
option, since that'll lead to the file being overwritten instead of just being renamed. An example of such a problem is shown below.
$ touch 1.png 3.png 25.png 100.png
$ rename -n 's/\d+/++$a/e' *.png
100.png not renamed: 1.png already exists
rename(1.png, 2.png)
25.png not renamed: 3.png already exists
rename(3.png, 4.png)
# oops, 2 files have disappeared!!
$ rename -f 's/\d+/++$a/e' *.png
$ ls
2.png 4.png
$ rm *.png
One of the ways to solve this issue is a two step process shown below. An extra unique string is added to the filenames, so that it cannot clash with existing filenames. The unique string is then removed afterwards.
$ touch 1.png 3.png 25.png 100.png
$ rename -n 's/\d+/"op_" . ++$a/e' *.png
rename(100.png, op_1.png)
rename(1.png, op_2.png)
rename(25.png, op_3.png)
rename(3.png, op_4.png)
$ rename 's/\d+/"op_" . ++$a/e' *.png
$ rename 's/op_//' *.png
$ ls
1.png 2.png 3.png 4.png
$ rm *.png
Exercises
1) Determine and implement the rename logic based on the filenames and expected output shown below.
$ touch ' (2020) Report part 1 . txt ' 'analysis Part 3 (2018) .log'
##### add your solution here
$ ls
2020_report_part_1.txt analysis_part_3_2018.log
2) See unix.stackexchange: rename Q&A sorted by votes for further reading as well as a source for exercises.