After importing several 1000 Files from a camera onto a hard drive I realized, that the counter, used in the process of renaming the file - does not start from 0. This leads to file structure like this:

My vacation 2018-05-03 2345.jpg
My vacation 2018-05-03 2346.jpg
My vacation 2018-05-04 2347.jpg

I would like to batch rename all those files in a wax, that the index starts with 0

My vacation 2018-05-03 0001.jpg
My vacation 2018-05-03 0002.jpg
My vacation 2018-05-04 0003.jpg

I went already through some topics dealing with batch renaming files and adding an counter/index (bash loop) or usig rename/prename but I was not able to get a working solution for my case.

Basically, I would like to match the part of the filename with the description and the date using the regular expression .*(\d\d\d\d\-\d\d\-\d\d){1} and add a suffix counter on the end.

  • Do you want the counter to start from 0 (My vacation 2018-05-03 0000.jpg) as you say, or from 1 as you show (My vacation 2018-05-03 0001.jpg)?
    – terdon
    Nov 7, 2021 at 17:17
  • @terdon, I suppose the idea is the counter starts at 0 but is incremented for every file before renaming, so the first file gets 1 Nov 8, 2021 at 12:13

4 Answers 4


With zsh:

$ autoload -Uz zmv
$ zmv -n '(* )(<->)(.jpg)' '$1${(l[4][0])$(($2 - 2344))}$3'
mv -- 'My vacation 2018-05-03 2345.jpg' 'My vacation 2018-05-03 0001.jpg'
mv -- 'My vacation 2018-05-03 2346.jpg' 'My vacation 2018-05-03 0002.jpg'
mv -- 'My vacation 2018-05-04 2347.jpg' 'My vacation 2018-05-04 0003.jpg'

(then, remove the -n (dry-run) if happy).

For a more explicit matching that requires the date be there in the file name (XXXX-XX-XX)

zmv -n '(* [0-9](#c4)(-[0-9](#c2))(#c2) )(<->)(.jpg)' '$1${(l[4][0])$(($3 - 2344))}$4'


zmv -n '(* <1970-2021>-<1-12>-<1-31> )(<->)(.jpg)' '$1${(l[4][0])$(($2 - 2344))}$3'

Those subtract 2344 to the number. Alternatively, you could just take them as they come in order and number them from 1 ignoring the value of the number that is already there. If you'd also want the number to restart at one each time the prefix before the date changes, you could do:

$ typeset -A n=()
$ zmv -n '((*) <1970-2021>-<1-12>-<1-31> )(<->)(.jpg)' '$1${(l[4][0])$((++n[\$2]))}$4'
mv -- 'My other vacation 2021-01-05 0044.jpg' 'My other vacation 2021-01-05 0001.jpg'
mv -- 'My other vacation 2021-01-05 0045.jpg' 'My other vacation 2021-01-05 0002.jpg'
mv -- 'My other vacation 2021-01-05 0046.jpg' 'My other vacation 2021-01-05 0003.jpg'
mv -- 'My other vacation 2021-01-05 0047.jpg' 'My other vacation 2021-01-05 0004.jpg'
mv -- 'My other vacation 2021-01-05 0048.jpg' 'My other vacation 2021-01-05 0005.jpg'
mv -- 'My vacation 2018-05-03 2345.jpg' 'My vacation 2018-05-03 0001.jpg'
mv -- 'My vacation 2018-05-03 2346.jpg' 'My vacation 2018-05-03 0002.jpg'
mv -- 'My vacation 2018-05-04 2347.jpg' 'My vacation 2018-05-04 0003.jpg'

In the file names, we need to substitute a sequence of digits followed by dot — \d+. — by a 4-zero padded counter followed by dot — sprintf("%04d.", ++$c).

rename -n -- 'our $c; s/\d+\./sprintf("%04d.", ++$c)/e' *.jpg

For no zero padding, we don't need sprintf, but only to concatenate the counter and the dot. Since the concatenation operator is also a dot,

rename -n -- 'our $c; s/\d+\./++$c . "."/e' *.jpg


Remove the -n when convinced it works correctly.

In some distributions rename may be called perl-rename.

our $c; was introduced to solve the 'Global symbol "$c" requires explicit package name' error. In my system it is not necessary... ¯\(ツ)

#!/usr/bin/env bash                                                                                                                     

#My vacation 2018-05-03 2345.jpg
#My vacation 2018-05-03 0002.jpg


# For every file.
for cfilename in ${cdir}/*; do
    # If its index starts with a 0.
    if [[ "${cfilename}" =~ "${filename_start} "[0-9]*-[0-9]*-[0-9]*" "[0] ]]; then
        # Change its name.
        mv "${cfilename}" "${cfilename}_my_addition";

Then you need to save this code as a file.
Give it .sh ending.

Give it permissions with:

chmod 700 rename_files.sh;

And then you can run it with:

  • Many thanks for your suggestion. I already tried something similar, but it would be good if I could pass the folder path and the "My vacation" string as a parameters. E.g. reindex.sh "Pictures/2018/My vacation" "My vacation" In a case the second parameter would be missing the last portion of the path would be used. Here is my gist gist.github.com/karlitos/957e307ba588d841082c55f0922799d0 butit is still not working properly
    – karlitos
    Nov 8, 2021 at 21:49
  • I updated the answer to have the script take in those arguments.
    – john-jones
    Nov 8, 2021 at 22:05
  • I am sorry, but your suggested solutions is still buggy. You are adding _my_addition suffix, instead of an counter. I tried to improve your script futher, please see the provided gist Sadly, it is still does not work properly
    – karlitos
    Nov 9, 2021 at 17:02
  • yeah that was on purpose. you were supposed to see it and realize you can replace it with whatever.
    – john-jones
    Nov 9, 2021 at 17:59

Beside all the suggested answers I also found the batch-rename functionality of Double Commander very usefull and suitable for my needs.

Here is my setup:

  • I add an counter to the filename [N][C]
  • I use search and replace
    • Search: (\d{1,4})(\d{2,4})(\..{3,4}$)
    • Replace: $2$3
  • You need to check both checkboxes Regular Expression and Use substitution

The instant preview is very helpful feature allowing fast tweaking and control of the resulting re-indexing of the files

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .