How to perform more complicated search and replace-style renaming in a batch file

Raymond Chen

Raymond

Last time, we looked at how copying and renaming with wildcards worked in MS-DOS, and how it doesn’t work well if you are trying to perform search-and-replace operations where the strings have different lengths.

For example, if you have a list of files, say fred001.txt through fred999.txt and you want to rename them to wilma001.txt through wilma999.txt, the obvious command

ren fred*.txt wilma*.txt

will not produce the desired results because the a in wilma overwrites the first character that was matched by the wildcard in the source pattern, since fred is only four characters long.

You can still get what you want; you just won’t be able to use the wildcard algorithm to do it.

setlocal enabledelayedexpansion
for %%i in (fred*.txt) do set "_=%%i" & ren "%%i" "!_:fred=wilma!"

We write a little batch file to perform the bulk rename operation.

The main loop is driven by the FOR command, which we ask to enumerate all the files that match the pattern fred*.txt. For each such file, we set the variable _ to the file name. I like to use _ as a scratch variable name in batch files because it’s unlikely to collide with a name that means something to any particular program.¹

We then perform a non-wildcard ren command. The source file name is the file name which the FOR command gave us. The destination file name is the result of a search-replace operation with the _ variable, where we ask to search for fred and change it to wilma.

This is a two-liner instead of a one-liner because we need to enable delayed expansion so that we can delay the search-replace operation until after the _ variable is set.

If I need to do some sort of fancy renaming, I don’t do any of this. I’ll do a dir /b and dump the list of file names into a file. Then I’ll edit that file and use the editor’s fancy search-replace features to convert it into a list of REN commands. I’ll look over the results to verify that they are doing what I want, and possibly perform some editing to deal with special cases like “Don’t rename fred314.txt; that one stays unchanged.” Once I’m satisfied, I save the results as a batch file and run it.

If the editing is particularly complicated, I’ll write a one-off program to generate the batch file. I prefer generating a batch file to having the one-off program perform the renames directly, because that lets me preview the operation. You don’t want to mess it up.

Bonus chatter: The reimagined Windows PowerToys includes an interactive bulk renaming tool called PowerRename.

¹ Sometimes people new to batch programming will have need for a temporary variable to hold a path, and they call it PATH. This tends to result in a lot of head-scratching, since they are unwittingly modifying the executable search path.

10 comments

Comments are closed. Login to edit/delete your existing comments

  • Avatar
    Peter Cooper Jr.

    I’ve actually, much more often than I would have expected, turned to Excel to help with these sorts of one-off build-a-script problems. Put the source in one column, in the next column write an expression (in this case, something like =CONCAT("ren ", A1, " ", SUBSTITUTE(A1, "fred", "wilma"))) and fill down for all the rows. Then copy everything in that column to a batch file (or directly to the command prompt, or to my SQL window, or whatever). I’ve certainly used my IDE’s “fancy search-replace features” for this sort of thing plenty of times too, though.

  • Avatar
    David Wolff

    An Emacs macro is also very powerful here.

    You can read in all the filenames, then do “start macro” (“ctl-x ctl-)”), edit a line, go next line, “end macro” (“ctl-x ctl-)”). Then “ctl-x e” to execute the macro on each line. You can also put a “REN ” in front of each line, open a shell buffer, and execute the line.

    Especially useful if you need to do this in more than one folder.

    There are more details, but I found this kind of thing to be an enormous timesaver.

  • Avatar
    Mike Morrison

    I’ve written many variations of that two-liner batch script over the years, but lately, I prefer to do my bulk renaming in a capable text editor, such as EditPad. I can use regex to bulk-modify SQL statements, REN commands, what have you. It’s just another way to accomplish the same thing.

  • Avatar
    Antonio Rodríguez

    I have a mkren.bat file which generates a ren.bat in the current directory, with one ren command for each file in it. It optionally accepts a pattern and/or a /d modifier to filter only directories. It saves me the trouble of creating the ren.bat file by hand importing a file list into my text editor and turning it into the ren commands. Stripping it from parameter parsing, online help and all the stuff, its core is just one line:

    for %mods% %%v in (%mask%) do echo ren "%%v" "%%v">> ren.bat

    Online help? Yes, of course. My memory isn’t very good, so I make all mi batch files so they can be run with the /? modifier to get a simple one line usage reminder:

    >mkren /?
    Usage: mkren /? | [/d] [{pattern}]
  • Avatar
    Fleet Command

    Ah, those days. I never learned the FOR command.

    Nowadays, if I wanted to do that, I just use PowerShell:

    Get-ChildItem "Fred*" | ForEach-Object { Rename-Item $_.FullName $($_.Name -replace "Fred","Wilma") }
    • Avatar
      Marcos Kirchner

      You probably know it, but you can also pipe results from Get-ChildItem directly into Rename-Item:

      Get-ChildItem "Fred*" | Rename-Item -NewName { $_.Name.Replace("Fred", "Wilma") }
      
      • Avatar
        Fleet Command

        Yeah. I was writing quickly. Wasn’t really thinking. I actually don’t do either of these, because they are risky when the file set is large. Imagine you wanted to change “Fred” into “Freddy”. You discover that your PowerShell script never ends. It changes the file names from Fred* to Freddy*, Freddydy*, Freddydydy*, Freddydydydy*, and so on. (Remember that “Freddy 01” sorts below “Fred 01”.)

        I’ve actually written a function for pattern-based renaming that looks like this:

        $Temp1=Get-ChildItem "Fred*"
        $Temp1 | ForEach-Object { Rename-Item $_.FullName $($_.Name -replace "Fred","Freddy") }

        Edit: I think I instinctively add a ForEach-Object because I usually insert a conditional statement in it.

    • Avatar
      Danstur

      The best part about that is that you can also simply add a -WhatIf to the rename command to see what would happen. Or add -Confirm and get asked before each rename if you really want to do it.

      Ok no, the best part is that it’s actually readable compared to the batch script.

  • Pavel Kostromitinov
    Pavel Kostromitinov

    I user self-written plugin for Far Manager, with basically regexp search and replace in file names