How Do You Rename Files from One Incrementing Pattern to Another?

I have a group of files that are named as follows:

048. banana.mkv
049. apple.mkv
050. mango.mkv
051. pear.mkv
052. grape.mkv

I need to rename those files with the following naming convention

01. banana.mkv
02. apple.mkv
03. mango.mkv
04. pear.mkv
05. grape.mkv

The problem is I have hundreds of files I need to rename and multiple groups that I need to start from 01 and increment. Generally I use rename to mass rename files. In this situation I have been using qvm to to change each filename. Surely there is an easier way.

2

2 Answers

Using the perl-based rename utility:

$ rename -n 's/^(\d+)/sprintf "%02d", $1-47/e' *.mkv
rename(048. banana.mkv, 01. banana.mkv)
rename(049. apple.mkv, 02. apple.mkv)
rename(050. mango.mkv, 03. mango.mkv)
rename(051. pear.mkv, 04. pear.mkv)
rename(052. grape.mkv, 05. grape.mkv)

The trick here is the e modifier on the s/pattern/replacement/ substitution - which lets you evaluate the replacement string as a perl expression - including arithmetic $1-47 and padded formating via sprintf. Remove the -n when you are satisfied that it is doing the right thing.

If you only have a shell, then:

$ for f in *.mkv; do 
    n="${f%%.*}"
    printf -v newf '%02d.%s' "$((10#$n-47))" "${f#*.}"
    echo mv -- "$f" "$newf" 
  done
mv -- 048. banana.mkv 01. banana.mkv
mv -- 049. apple.mkv 02. apple.mkv
mv -- 050. mango.mkv 03. mango.mkv
mv -- 051. pear.mkv 04. pear.mkv
mv -- 052. grape.mkv 05. grape.mkv

Remove the echo once you are happy that it's doing the right thing.


With zsh and zmv

% autoload zmv
% setopt expanded_glob
%
% zmv -n '(*[0-9])(*.mkv)' '${(l:2::0:)$(( $1-47 ))}$2'
mv -- '048. banana.mkv' '01. banana.mkv'
mv -- '049. apple.mkv' '02. apple.mkv'
mv -- '050. mango.mkv' '03. mango.mkv'
mv -- '051. pear.mkv' '04. pear.mkv'
mv -- '052. grape.mkv' '05. grape.mkv'

In this case, the number formating is done using the l (letter ell) expansion modifier - from man zshexpn:

   l:expr::string1::string2:
          Pad the resulting words on the left.  Each word  will  be  trun‐
          cated if required and placed in a field expr characters wide.

As with rename, remove the -n to actually change the names.

2

I presume the start number needn't always be 048, in which case you might want a command you can run--without modification--in each directory and always does the right thing.

You can run this in each directory:

file-rename -n 's/^\d{3}\./sprintf "%02d.", ++our $i/e' -- *.mkv

This resembles steeldriver's answer, the major difference being that it figures out where to start for you. There are other minor differences. Note that if there are gaps in the input numbering, this will fill those gaps in the output numbering, which may or may not be what you want. That is, if your files were 048. banana.mkv, 049. apple.mkv, and 051. mango.mkv, the output would be 01. banana.mkv, 02. apple.mkv, and 03. mango.mkv (not 04. mango.mkv).

As written, that command doesn't rename any files, due to the -n option. Instead, it shows what it would do. If you're happy with this, you can run it again without -n:

file-rename 's/^\d{3}\./sprintf "%02d.", ++our $i/e' -- *.mkv

You might not have the file-rename command. If you don't then you most likely have no rename command or (and this is why I recommend using file-rename over rename) your rename command may be something altogether different. If you don't have file-rename (or if you prefer to write it as rename and don't have that), you can get it by installing the rename package:

sudo apt update && sudo apt install rename

How It Works

When your shell expands a glob pattern like *.mkv into a list of filenames, the resulting list is in lexicographic order. Exactly what that means depends on your current locale, and in general it is not the same as numeric order even where both are applicable. However, since your filenames are numbered at the very beginning and all the numbers are padded on the left with 0s, this issue won't arise, and your shell expands *.mkv into a numerically ordered list of paths.

Since the shell takes care of this, the remaining precaution is to ensure file-rename avoids changing any files that don't start with a sequence of three digits followed by a ..

In the substitution expression s/^\d{3}\./sprintf "%02d.", ++our $i/e:

  • ^\d{3}\. is a regular expression. It matches a literal . (\.) occurring just after three ({3}) digits (\d) at the start of the filename (^).

  • sprintf "%02d.", ++our $i is Perl code.

    It uses a global variable $i, introducing it as such if not already seen (our). $i starts with the value zero, but is incremented each time it is used, immediately before it is used (prefix ++ does this). So it takes on the values 1, 2, 3, and so forth, for each file.

    sprintf is used to format it, if needed padded on the left with zeros, to a width of 2, followed by a literal .. (. need only be escaped in a regular expression, where otherwise it would match any character, and not in an expression for substitution.

  • e at the end causes the expression to be evaluated as Perl code to produce the result for substitution. Without this e, the actual text starting with sprintf would be substituted into each filename, rather than the result of evaluating that text as Perl code.

Files whose names don't start with three digits followed by a . are not renamed and do not cause $i to be incremented. Filenames that don't end in an .mkv suffix aren't even passed to file-rename in the first place.

Maya Lin-Takahashi

Maya Lin-Takahashi

Consumer Tech & Gadget Reviewer

Maya is a hardware enthusiast who tests and reviews smart home devices, smartphones, wearables, and audio gear. She focuses on practical consumer value and build quality.