3

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
  • ...but non of the group is bigger than 99?
    – user986805
    Jun 9, 2020 at 17:15
  • correct... Most of the file groups are around 25 - 30 files Jun 9, 2020 at 17:29

2 Answers 2

3

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
  • This works exactly how I want it to. I use rename on a daily basis and could never come up with that. got any recommendations on a book or something explain how I can learn to do this on my own? Jun 9, 2020 at 18:15
  • @GooberHimself the trick here is the e modifier on the s/pattern/replacement/ substitution - which lets you evaluate the replacement string as a perl expression. For regex stuff in general I find Regular-Expressions.info the best general resource. Jun 9, 2020 at 18:23
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.

Automating It

You may just want to run this once per directory, since it sounds like you have only a handful of directories. But if you want to automate it, you can. This can be done with find, though it's trickier and more complicated than it might seem at first, because of what paths look like from find (for example, with -execdir you'll get paths like ./048. banana.mkv), and because -exec/-execdir with + will split into multiple commands (resetting $i undesirably) in the event of very big directories (which you don't have, but which is another factor that has to be considered).

So you may just want to use a shell loop to run it:

for d in dir1 dir2; do
    printf '%s:\n' "$d"
    (cd -- "$d" && file-rename -n 's/^\d{3}\./sprintf "%02d.", ++our $i/e' -- *.mkv)
done

(That would also not work for directories with huge numbers of *.mkv files, but it would give error messages for those directories rather than doing the wrong thing.)

Replace dir1 dir2 with your actual list of directories containing files you want to rename.

This produces output like:

dir1:
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)
dir2:
rename(099. foo.mkv, 01. foo.mkv)
rename(100. bar.mkv, 02. bar.mkv)
rename(101. baz.mkv, 03. baz.mkv)

If that looks good, you can remove -n, as before. (You'll still see the directory-name headings, but not the rename(..., ...) lines. If you want to see those too during the actual rename, you can pass the -v option to file-rename, i.e., replace -n with -v instead of merely removing -n. This may make it harder to notice any error messages, though.)

Further Reading

  • Bulk renaming files in Ubuntu; the briefest of introductions to the rename command (by Oli) shows and explains several ways to use rename, including with sprintf.

    That applies to both the current Perl renamer file-rename and the old one prename. (In the latest Ubuntu releases, file-rename and prename are the same, but in older releases they were different and had somewhat different behavior.) In Ubuntu, rename has by default either not been present or been a symlink that resolves to one of those. It does not apply to rename.ul, the other utility that on some systems--and on Ubuntu if you configure it to do so--runs when you run rename.

  • For information about regular expressions in Perl, I recommend perldoc perlrequick and perldoc perlre. These cover the syntax of s/ expressions.

  • For information about regular expressions in general, I recommend https://www.regular-expressions.info.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.