Bulk renaming files with the rename command

Combining Multiple Operations

As shown in Figure 3, the preceding example only reformatted the dates. I still need to insert the colons between each time component. Again, I can use back references, as follows:

rename 's/([0-9]{2})([0-9]{2})([0-9]{2})$/$1:$2:$3/' *

This command certainly works, but what if I want to use one single rename command to do both the date and time manipulation instead of running two separate rename commands in sequence? Certainly, I could combine the two search expressions into one very long search expression, but this quickly becomes cumbersome and very difficult to read:

rename 's/([0-9]{4})([0-9]{2})([0-9]{2})_([0-9]{2})([0-9]{2})([0-9]{2})$/$3-$2-$1_$4:$5:$6/' *

Fortunately, it is possible to perform both tasks using one command but keep the tasks logically separated. If each expression is separated by a semicolon character, rename can execute two or more expressions in one command:

rename 's/([0-9]{4})([0-9]{2})([0-9]{2})/$3-$2-$1/;
s/([0-9]{2})([0-9]{2})([0-9]{2})$/$1:$2:$3/' *

(Note the new line after the semicolon character. While not necessary, it improves the readability of the search expression; rename interprets it as a harmless whitespace character).

Transliterating Characters

The y/// command transliterates text. It looks for each character specified in the command's first parameter and replaces any instance of that character with the corresponding character in the second parameter. For example, to replace any As with Zs and any Zs with As in file names, use:

rename 'y/AZ/ZA/' *

After executing this command, the file ZAGREB.TXT becomes AZGREB.TXT.

While the y/// command is case-sensitive like s///, the y/// command does not have an option switch to enable case-insensitivity (see the "s/// Options" box for more information). Thus, the above y/// command will replace ZAGREB.TXT but not zagreb.txt. Furthermore, it will change Zagreb.txt to Aagreb.txt, but not to Azgreb.txt as you may expect. To do that, you would need to change the command to:

rename 'y/AZaz/ZAza/' *

One common use of y/// is to convert uppercase file names to lowercase, or vice versa, which is useful for old MS-DOS or early Windows files that saved files in all uppercase characters. You can implement such transliteration by specifying the entire alphabet in the command explicitly, but doing so is cumbersome because that would require typing out at least 52 letters: the 26 uppercase letters in the search expression, and the 26 lowercase letters in the replacement expression. Instead, you can specify ranges of characters in the search expression, as in y/[A-Z]/[a-z]/ (to replace uppercase characters with their lowercase equivalents).

Like the s/// command, the y/// command accepts one or more options following the final slash of the command. None of these options are likely to be useful for general purposes, but c and d might have some niche uses (see the "y/// Options" box).

y/// Options

Like the s/// command, the y/// command accepts a few option characters; each option alters the behavior of the y/// command in its own way. The y/// options are rarely useful, but two options, c and d, might come in handy.

Both of these options are used in connection with an intrinsic behavior of y/// known as squashing: If the number of characters on the replacement list is less than the number of characters on the search list, the last character on the replacement list is duplicated until the search and replacement lists are equal in length. For example,

y/[A-Z]/x/

is equivalent to:

y/[A-Z]/xxxxxxxxxxxxxxxxxxxxxxxxxx/

Both expressions will replace any uppercase letter with a lowercase x character. The first, however, is much more compact and easier to read.

One potentially useful option, c, instructs y/// to complement the list of characters on the search list and replace any character that is not present on the list. When combined with squashing, this can be used to change forbidden characters not explicitly on the search list to one particular placeholder character. For instance, if you have files with unprintable characters in their names (*nix/Linux filesystems can handle most non-printable characters in file names), you can quickly clean up the file names by replacing all non-alphabetic, non-numeric, non-underscore/hyphen characters in the file names with dot (.) characters, as in:

y/[A-Z][a-z][0-9]_-/./c

Another potentially useful option, d, disables squashing and deletes any character on the end of the replacement list that has no corresponding character on the search list. Thus, y/_.[A-Z]/_.[a-d]/d will convert the file name DOC_1993.BAK into dc_.ba. While this example is contrived, it is the nature of an option switch with limited practical utility.

Moving Files Between Directories

Another potential use of rename is to have each category of logfile placed in its own directory. In Listing 4, I have several dated logfiles named daemon, syslog, and messages. While I currently only have five logfiles in that directory, I could eventually end up with hundreds or even thousands of logfiles to manage. Consequently, I want to move each type of logfile into its own directory (e.g., I want syslog_13-10-2019_23:36:11 to be moved into a directory called syslog). Ideally, I would also like the initial part of the logfile's name to be removed because the containing directory's name should make clear the type of logfile. Listing 5 shows the desired resulting directory tree.

Listing 5

Separating into Subdirectories by Name

$ ls -FNR
.:
daemon/  messages/  syslog/
./daemon:
09-03-2020_07:18:42
./messages:
02-04-2023_09:32:00  13-12-2021_13:43:27
./syslog:
13-10-2019_23:36:11  26-07-2022_18:56:03

Fortunately, rename can move files just as easily as it can rename them. In fact, it can do both in the same step. Obviously, I want to do both simultaneously in this case, because I want to move the file and then remove the first part of the file name.

Unfortunately, to move a file to another directory, rename requires that the destination directory already exist; rename will not create the directory for you. Prior to running rename, you will have to pre-create all the necessary directories. I used the following shell one-liner to create the directories before running rename:

find . -maxdepth 1 -type f -printf '%f\0' | grep -Eoz '^[^_]+' |xargs -0 mkdir

This one-liner lists all files immediately under the current directory – not any files under subdirectories – and then takes the part of the file name up to the first underscore (e.g., messages), and creates a new directory in the current directory named after the first part of the file name.

Now, to move each logfile and then remove the initial part of each file name, I use:

rename 's/^([^_]+)_/$1\//' *

There are several things to note here. The first is that I instructed rename to search for any length of string at the very beginning of the file name that does not contain an underscore (the ^([^_]+) in the search expression). This takes advantage of the fact that the logfile type is separated from the date by an underscore. I then use a back reference followed by a slash in the replacement expression to tell rename to move the file into a directory named after whatever was matched by the aforementioned parenthesized expression.

Note how I escaped the slash character (as in \/) to guarantee that rename does not mistake the slash as the end of the replacement expression. Remember, the search and replacement expressions, as well as any options to the s/// command, are separated by slash characters, just like file-name components are separated by slashes. Actually, I could have used virtually any character to separate the parts of the s/// command; while using slashes is the common convention, I also could have used at signs (@) in the rename command above, or in any of the previous s/// commands. The following would have worked just as well:

rename 's@^([^_]+)_@$1/@' *

By using a character other than the slash to separate the parts of the s/// command, I no longer have to escape the slash in the replacement expression that denotes part of a directory path. In my opinion, this makes the command a bit easier to read. Just make sure that the character that you choose appears neither in the search or replacement expression (or is escaped where it appears).

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Bulk Renaming

    When it comes to renaming multiple files, the command line offers time-saving options in the form of mv, rename, and mmv.

  • Programming Snapshot – Bulk Renaming

    Renaming multiple files following a pattern often requires small shell scripts. Mike Schilli looks to simplify this task with a Go program.

  • Bulk Renamers
  • Command Line: Grep

    Once you understand the intricacies of grep, you can find just about anything.

  • Admin Workshop: Finding Files

    Modern computers with their multiple Gigabyte hard disks store thousands of files. A lost file can cause a lot of work,and it can also pose a security risk. Fortunately,Linux has some versatile tools for finding those “lost files.”

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News