I want to rename files with zero padded numbers while keeping extension.

to be renamed as

:~/x$ rename -n -v 's/.+/our $i; sprintf("%03d.jpg", 1+$i++)/e' *
rename(a.abc, 001.jpg)
rename(b.cde, 002.jpg)
rename(c.xyz, 003.jpg)
:~/x$ echo "a.abc"
:~/x$ echo ${_##*.}
so I tried>
:~/x$ rename -n -v 's/.+/our $i; sprintf("%03d.${_##*.}", 1+$i++)/' *
Global symbol "$i" requires explicit package name (did you forget to declare "my $i"?) at (user-supplied code).
Missing right curly or square bracket at (user-supplied code), within string
syntax error at (user-supplied code), at EOF

Any suggestions using "rename" command?

  • 1
    You're trying to stuff the shell variable expansion ${_##.*} into perl. Mar 5, 2019 at 21:08

2 Answers 2

rename -n -v 'our $n; my $zn=sprintf("%03d", ++$n); s/[^.]*/$zn/' *

This would probably do what you intended. Instead of putting the Perl code inside the substitution, we run it before the substitution.

The regular expression [^.]* would match any length string up to (but not including) the first dot in the filename.

To match up to the last dot, use .*\. instead, and insert the dot on the replacement side:

rename -n -v 'our $n; my $zn=sprintf("%03d", ++$n); s/.*\./$zn./' *

Note that this would also rename directories.

Alternatively, using a simple shell loop, assuming you would want to enumerate the files in the order they are expanded by the * shell glob, and that you use bash:

for filename in *; do
    [ ! -f "$filename" ] && continue
    zn=$( printf '%03d' "$n" )
    mv -i -- "$filename" "$zn.${filename##*.}"
    n=$(( n + 1 ))

This additionally skips any name that does not refer to a regular file (or a symbolic link to one). Apart from that, it follows very closely the Perl rename variation above in that it keeps a counter (n) and a zero-filled variant of the counter (zn).

The variable n is a simple counter, and $zn is has the same value as $n, but as a zero-filled three-digit number.

The value of $zn.${filename##*.} would expand to the zero-filled number, followed by a dot and the final filename suffix of the original filename. If more than one dot is present in the original filename, everything up to the last dot will be replaced by the zero-filled number. Change ## to # to replace up to the first dot.

This assumes that you run the loop on files in the current directory only.

  • 1
    @VeeJay Read again.
    – Kusalananda
    Mar 5, 2019 at 18:04
  • 1
    Sorry I was on mobile didn't read properly. Made my day.
    – Vijay
    Mar 5, 2019 at 18:50
  • Rename command-lines are perfect. Please check the mv cmd, it didn't work. Out put mv: overwrite '001.abc'? after first file.
    – Vijay
    Mar 5, 2019 at 18:58
  • @VeeJay Ah, thanks for that. I thought I simplified the code, but in fact I broke it. Now fixed.
    – Kusalananda
    Mar 5, 2019 at 19:17

You could do

rename -n -v 'our $i; s{^\./.+?(\.[^.]*)?\z}{sprintf "%03d%s", ++$i, $1}se' ./*
  • ./ prefix in ./*: needed for some variants of rename that would fail otherwise for filenames starting with -.
  • .+?: match at least one character, as we don't want a leading . (for hidden files, only relevant if the dotglob option of your shell is enabled) to be considered as an extension separator.
  • (\.[^.]*)?: also rename files without extension (like .foo or foo)
  • \z: match end of the subject. More generally with rename, you don't want to use $ as it matches either at the end of the subject or before a trailing newline.
  • s flag: make sure . also matches a newline character which is as valid as any in a file name.
  • e flag: the replacement is a perl expression, so we can use sprintf there.

You must log in to answer this question.

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