Taco Steemers

A personal blog.
☼ / ☾

Mass file renaming in Linux

I have a collection of notes. Unfortunately the naming scheme is not handy. When we sort the files by name, they are sorted by the number for the day of the month. Instead, we want them sorted by the year first, then the number of the month, and finally the number for the day of the month.

We start with this situation:

$ ls
02-aug-2021.txt  06-jul-2021.txt  08-jul-2021.txt  12-jul-2021.txt  15-jun-2021.txt  17-jun-2021.txt  21-jul-2021.txt  22-jul-2021.txt  
23-jul-2021.txt  24-jun-2021.txt  30-jul-2021.txt 05-aug-2021.txt  07-jul-2021.txt  09-jul-2021.txt  14-jun-2021.txt  16-jun-2021.txt  
18-jun-2021.txt  21-jun-2021.txt  22-jun-2021.txt  23-jun-2021.txt  25-jun-2021.txt  30-jun-2021.txt

We want to change 02-aug-2021.txt to 2021-08-02.txt. That way the filename sort order is also the chronological order.

To achieve this we install Debian's verison of the rename tool:

$ sudo apt install rename

To find out how to use a command like rename we can open the manual. Most commands do have a manual entry. We open the manual for rename by executing:

$ man rename

It will show information about the command, and the flags that we can pass it. Sometimes it also gives examples.

We can give the rename command a regex to switch the date-related parts of the filenames to new locations. Then we use the nono flag to avoid actually executing the command, and the verbose flag to see the output.

The regex needs to create a match group for two digits (group 1), then three words (group 2), and then four digits (group 3). Then we can put these match groups in their intended locations. To create the first match group for two digits, we write \d{2}. To place that match in the output, we write \$1. This is what the total regex looks like: s/(\d{2})-(\w{3})-(\d{4}).txt/\$3-\$2-\$1.txt/ The pattern on the left-hand side of the middle / is the pattern we match on, with the three match groups. The - characters will match with the - characters in the file names. The pattern on the right-hand side is the output pattern.

Let's try this out:

$ rename --nono --verbose "s/(\d{2})-(\w{3})-(\d{4}).txt/\$3-\$2-\$1.txt/" *.txt
rename(02-aug-2021.txt, 2021-aug-02.txt)
rename(05-aug-2021.txt, 2021-aug-05.txt)
...

Looks good! The output tells us that we will change 02-aug-2021.txt into 2021-aug-02.txt. Now we know the result will be good, and we can perform the command.

$ rename "s/(\d{2})-(\w{3})-(\d{4}).txt/\$3-\$2-\$1.txt/" *.txt
$ ls
2021-aug-05.txt  2021-aug-02.txt  2021-jul-06.txt  2021-jul-08.txt  2021-jul-12.txt  2021-jul-22.txt  2021-jul-30.txt  2021-jun-15.txt  
2021-jun-17.txt  2021-jun-21.txt  2021-jun-23.txt  2021-jun-25.txt 2021-aug-05.txt  2021-jul-07.txt  2021-jul-09.txt  2021-jul-21.txt  
2021-jul-23.txt  2021-jun-14.txt  2021-jun-16.txt  2021-jun-18.txt  2021-jun-22.txt  2021-jun-24.txt  2021-jun-30.txt

As you can see we still have one step remaining in this particular file renaming example. We also want the months as numbers. I'm quite certain that there is a number of fancy ways to do that. I am just going to do it the simple way. I will swap jul with 07, and so on.

$ rename "s/(\d{4})-jul-(\d{2}).txt/\$1-07-\$2.txt/" *.txt
... and again for the other months
$ ls
2021-06-14.txt  2021-06-17.txt  2021-06-22.txt  2021-06-25.txt  2021-07-07.txt  2021-07-12.txt  2021-07-23.txt  2021-08-05.txt
2021-06-15.txt  2021-06-18.txt  2021-06-23.txt  2021-06-30.txt  2021-07-08.txt  2021-07-21.txt  2021-07-30.txt  2021-08-10.txt
2021-06-16.txt  2021-06-21.txt  2021-06-24.txt  2021-07-06.txt  2021-07-09.txt  2021-07-22.txt  2021-08-02.txt

Mission accomplished!