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

info See also: askubuntu: What's the difference between the different "rename" commands?

info See also F2: a cross-platform tool for batch renaming files and directories quickly and safely.

Basic example and sanity check

info 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

info 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.