I am trying to batch-rename a bunch of files in my shell, and even though there is plenty of material about it on the internet, I cannot seem to find a solution for my specific case.

I have a bunch of files that have (what appears to be) a "timestamp-id":


that I'd like to exchange for a counter:


My (plenty) naïve approach would be something like:

mv abc_*.png abc_{001..123}.png

Also, I could not figure out a way to make it work with a for-loop.

FWIW, unfortunately rename is not available on this particular system.

Any advice would be greatly appreciated!

  • FWIW: abc_*.png abc_{001..123}.png expands to the existing file names, and then the generated names in sequence, and mv has no way to determine what the distinction between them is. (Try e.g. echo abc_*.png abc_{001..123}.png) – ilkkachu Jan 31 '18 at 12:36

I can't think of a solution that handles incrementing the counter in a more clever way, but this should work:

for fi in abc_??????.png; do
    mv "$fi" abc_$i.png

It should be safe to use abc_*.png because it is expanded before the first mv is ever executed, but it can be useful to be very specific in that you only want files with a six-character timestamp at the end.

| improve this answer | |
  • 5
    Change the destination file name to "$(printf "abc_%03d.png" "$i")" to get the zero-padding, too – ilkkachu Jan 31 '18 at 12:19
  • I had been very close to this solution at one point… The zero-padding is the cherry on top! Thanks1 – NicApicella Jan 31 '18 at 12:23
  • Minor point: the line that increments $i can be reduced to just ((i++)) – teracow Oct 28 '19 at 23:29

With rename utility as part of Perl packages, you would do:

rename -n 'our $i; s/_.*/sprintf("_%03d.png", $i++)/e' *.png

Note: -n is for dry run, remove it to rename apply on files.

| improve this answer | |

With zsh:

typeset -A count
(zmv -n '([^0-9]##)<->(*)(#qn)' '$1${(l:3::0:)$((incr))}$2')

Remove the -n when happy.


$ ls
a1b.png  abc_128390.png  abc_159084.png  x12y.png
a2b.png  abc_138493.png  a.png           x2y.png
$ typeset -A count
$ incr='++count[$1/$2]'
$ (zmv -n '([^0-9]##)<->(*)(#qn)' '$1${(l:3::0:)$((incr))}$2')
mv -- a1b.png a001b.png
mv -- a2b.png a002b.png
mv -- abc_128390.png abc_001.png
mv -- abc_138493.png abc_002.png
mv -- abc_159084.png abc_003.png
mv -- x2y.png x001y.png
mv -- x12y.png x002y.png
| improve this answer | |

Avoid overwriting existing files:

for fi in abc_??????.png; do
    a="abc_$(printf '%04d' "$i").png"
    if [[ -e $a ]]; then
        echo "file $a exist, not moving $fi"
        mv "$fi" "$a"
| improve this answer | |

you can use cut to cut out parts of the filename, for example, if you want to rename files like

"1 first.jpg"
"2 second.jpg"


"10 first.jpg"
"20 second.jpg"

you can use

for i in *jpg; do
  mv -iv "$i" "$(echo "$i"|cut -d\  -f1)0 $(echo "$i"|cut -d\  -f2-99)";
| improve this answer | |
  • i like this approach, but I think your delimiter choice is causing the whole filename to be appended. Maybe use the -c1 and -c2-99 flags for character instead of field? Prob -c2- would capture to end of line. – Merlin Dec 24 '19 at 22:03

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.