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.mkvI need to rename those files with the following naming convention
01. banana.mkv
02. apple.mkv
03. mango.mkv
04. pear.mkv
05. grape.mkvThe 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 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.mkvRemove 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.
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' -- *.mkvThis 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' -- *.mkvYou 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 renameHow 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 $iis Perl code.It uses a global variable
$i, introducing it as such if not already seen (our).$istarts 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.sprintfis 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.eat the end causes the expression to be evaluated as Perl code to produce the result for substitution. Without thise, the actual text starting withsprintfwould 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 withsprintf.That applies to both the current Perl renamer
file-renameand the old oneprename. (In the latest Ubuntu releases,file-renameandprenameare the same, but in older releases they were different and had somewhat different behavior.) In Ubuntu,renamehas by default either not been present or been a symlink that resolves to one of those. It does not apply torename.ul, the other utility that on some systems--and on Ubuntu if you configure it to do so--runs when you runrename.For information about regular expressions in Perl, I recommend
perldoc perlrequickandperldoc perlre. These cover the syntax ofs/expressions.For information about regular expressions in general, I recommend .