Search code examples
macosshellfile-renamebatch-rename

How to batch rename files in a terminal?


I want to rename the files in a directory to sequential numbers.

stars_01_012.png
stars_01_014.png
stars_01_015.png
stars_01_017.png
stars_02_012.png
stars_02_014.png
stars_02_015.png
stars_02_017.png

And change it into

stars_01_001.png
stars_01_002.png
stars_01_003.png
stars_01_004.png
stars_02_001.png
stars_02_002.png
stars_02_003.png
stars_02_004.png

Relatives but not completely: How to Batch Rename Files in a macOS Terminal? How can I batch rename files using the Terminal?


Solution

  • You can do it with rename which you can install on macOS with homebrew using:

    brew install rename
    

    The command then looks like this:

    rename --dry-run -X -e 'my @parts=split /_/; my $this=$parts[0].$parts[1]; if(!defined($ENV{previous}) || $this ne $ENV{previous}){$ENV{previous}=$this; $ENV{N}=0}; $ENV{N}=$ENV{N}+1; $_ = $parts[0]."_".$parts[1]."_".$ENV{N}' *png
    

    Sample Output

    'stars_01_012.png' would be renamed to 'stars_01_1.png'
    'stars_01_014.png' would be renamed to 'stars_01_2.png'
    'stars_01_015.png' would be renamed to 'stars_01_3.png'
    'stars_01_017.png' would be renamed to 'stars_01_4.png'
    'stars_02_012.png' would be renamed to 'stars_02_1.png'
    'stars_02_014.png' would be renamed to 'stars_02_2.png'
    'stars_02_015.png' would be renamed to 'stars_02_3.png'
    'stars_02_017.png' would be renamed to 'stars_02_4.png'
    'stars_88_099.png' would be renamed to 'stars_88_1.png'
    

    Explanation:

    my @parts=split /_/ splits the filename into 3 parts using the underscore as the separator,

    my $this=$parts[0].$parts[1] saves the first two parts simply concatenated together, e.g. "stars01",

    the if statement tests if either of the first two parts have changed, then

    $ENV{previous}=$this; $ENV{N}=0 saves the current stem of the filename in an environment variable called previous and the current sequential counter in another environment variable N,

    $ENV{N}=$ENV{N}+1 increments the sequential counter, and

    $_ = $parts[0]."_".$parts[1]."_".$ENV{N} creates the new output filename from the various parts.

    If that all looks correct, remove the --dry-run and run it again - probably in a spare directory with a copy of your files until you are sure all is ok :-)


    The above may be easier to read like this:

    #!/bin/bash
    
    rename --dry-run -X -e '
          my @parts=split /_/;                      # split filename into parts based on underscore
          my $this=$parts[0].$parts[1];             # save first two parts of filename in $this
                                                    # see if it is a new stem
          if(!defined($ENV{previous}) || $this ne $ENV{previous}){
             $ENV{previous}=$this; $ENV{N}=0};      # save new initial part, reset N
          $ENV{N}=$ENV{N}+1;                        # increment N
          $_ = $parts[0]."_".$parts[1]."_".$ENV{N}  # formulate new filename from parts
        ' *png
    

    Change the last line to the following if you want to zero-pad the numbers out to three digits:

      $_ = sprintf("%s_%s_%03d",$parts[0],$parts[1],$ENV{N})  # formulate new filename from parts
    

    Note:

    I save the previous file prefix and sequential counter into environment variables to preserve them between files - there may be easier ways - if anyone knows, please ping me! Thanks.