6

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":

abc_128390.png
abc_138493.png
abc_159084.png
...

that I'd like to exchange for a counter:

abc_001.png
abc_002.png
abc_003.png
...

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!

ilkkachu
  • 138,973
  • 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

5 Answers5

8

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

i=0
for fi in abc_??????.png; do
    mv "$fi" abc_$i.png
    i=$((i+1))
done

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.

8

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.

αғsнιη
  • 41,407
  • What does the our $i mean? I couldn't get this to work. – Sruly May 01 '23 at 22:15
  • @Sruly that's a local variable definition used in the rename script. – αғsнιη May 02 '23 at 02:34
  • after writing the comment here I found that there are a few different rename comments around. Mine is from linux-utils. I'm assuming your answer refers to one of the perl scripts – Sruly May 02 '23 at 18:14
4

With zsh:

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

Remove the -n when happy.

(note the extra step using the incr variable to avoid the ACE vulnerability)

Example:

$ 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
1

Avoid overwriting existing files:

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

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"
...

to

"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)";
done
rubo77
  • 28,966
  • 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