13

I've tried a bunch of examples of the rename command but I can't figure out the syntax to do what I want.

I have a bunch of files labeled something like

File_Ex_1.jpg
File_Ex_2.jpg
File_Ex_3.jpg
File_Ex_4.jpg
...
File_Ex_10.jpg
File_Ex_11.jpg
etc. 

I want to change only some of them by inserting a 0 so that the file names have the same number of characters.

So I want File_Ex_1.jpg to go to File_Ex_01.jpg, File_Ex_2.jpg to File_Ex_02.jpg

How do you do this with the rename command?

This rename command was installed via home-brew on a Mac. Output of rename -v:

rename -v
Usage:
    rename [switches|transforms] [files]

    Switches:

    -0/--null (when reading from STDIN)
    -f/--force or -i/--interactive (proceed or prompt when overwriting)
Wide character in print at /System/Library/Perl/5.18/Pod/Text.pm line 286.

    -g/--glob (expand "*" etc. in filenames, useful in Windows™ CMD.EXE)
    -k/--backwards/--reverse-order
    -l/--symlink or -L/--hardlink
    -M/--use=*Module*
    -n/--just-print/--dry-run
    -N/--counter-format
    -p/--mkpath/--make-dirs
    --stdin/--no-stdin
    -t/--sort-time
    -T/--transcode=*encoding*
    -v/--verbose

    Transforms, applied sequentially:

    -a/--append=*str*
    -A/--prepend=*str*
    -c/--lower-case
    -C/--upper-case
    -d/--delete=*str*
    -D/--delete-all=*str*
    -e/--expr=*code*
    -P/--pipe=*cmd*
    -s/--subst *from* *to*
    -S/--subst-all *from* *to*
    -x/--remove-extension
    -X/--keep-extension
    -z/--sanitize
    --camelcase --urlesc --nows --rews --noctrl --nometa --trim (see manual)
toombzie
  • 131

5 Answers5

25

Try with this:

rename -e 's/\d+/sprintf("%02d",$&)/e' -- *.jpg

Example:

$ ls
Device_Ex_10.jpg  Device_Ex_1.jpg  Device_Ex_4.jpg  Device_Ex_7.jpg
Device_Ex_11.jpg  Device_Ex_2.jpg  Device_Ex_5.jpg  Device_Ex_8.jpg
Device_Ex_12.jpg  Device_Ex_3.jpg  Device_Ex_6.jpg  Device_Ex_9.jpg
$ rename -e 's/\d+/sprintf("%02d",$&)/e' -- *.jpg
$ ls
Device_Ex_01.jpg  Device_Ex_04.jpg  Device_Ex_07.jpg  Device_Ex_10.jpg
Device_Ex_02.jpg  Device_Ex_05.jpg  Device_Ex_08.jpg  Device_Ex_11.jpg
Device_Ex_03.jpg  Device_Ex_06.jpg  Device_Ex_09.jpg  Device_Ex_12.jpg

I took the reference from here: https://stackoverflow.com/questions/5417979/batch-rename-sequential-files-by-padding-with-zeroes

Here adapted to your particular rename implementation.

  • This looks like a proper call for the Perl-based version of rename, which the asker doesn't appear to have. – dhag Feb 22 '17 at 21:20
  • 1
    Is there a good reference to explain what these commands mean? Like what the + sign does.

    This is close, except I guess the real format for my filenames is

    Device1_Ex_1.jpg

    Where this syntax would turn that into Device01_Ex_1.jpg and not Device1_Ex_01.jpg

    – toombzie Feb 22 '17 at 21:21
  • 1
    @dhag The asker definitely has a Perl-based version, both because they aren't running Linux so they don't have the util-linux rename, and because the output of rename -v explicitly mentions Perl. – Gilles 'SO- stop being evil' Feb 22 '17 at 22:24
  • 1
    Yes, OK, my wording was terrible. I should have said "the version that accepts a Perl expression as one of its parameters". This still looks like a wrong answer, doesn't it? – dhag Feb 22 '17 at 22:44
  • Interestingly, while this appears to contradict the help text which, if I understand correctly, requires -e before the Perl code, this call actually works with the rename utility that comes with macOS. I retract my initial comment. – dhag Feb 23 '17 at 14:04
  • This is the method I've been using to fix episode numbers on my plex server. It's made me love rename. Although I would've done it as "rename -e 's/(\d+)/sprintf("%02d",$1)/e' *.jpg". More or less the same though. – Jason Feb 23 '17 at 14:33
  • OK, this works. Thanks! Wish I knew what the commands mean though -- reading the perl regular expressions syntax page doesn't make it very clear on how to write the expressions. – toombzie Feb 23 '17 at 14:51
  • This doesn't work if order needs to be maintained, since *.jpg will order 'Device_Ex_9.jpg' after 'Device_Ex_99.jpg'. See my answer below for a fix. – Mr Squid Jan 13 '21 at 21:17
  • sprintf isn't a thing on zsh on Mac – geoidesic Aug 19 '22 at 21:57
6

With the version of rename that you seem to be using, the following expression should do the transformation you request (example with a directory containing files named File_Ex_{1..11}.jpg):

$ rename -n -e 's/_(\d\.)/_0$1/g' -- *.jpg
'File_Ex_1.jpg' would be renamed to 'File_Ex_01.jpg'
'File_Ex_2.jpg' would be renamed to 'File_Ex_02.jpg'
'File_Ex_3.jpg' would be renamed to 'File_Ex_03.jpg'
'File_Ex_4.jpg' would be renamed to 'File_Ex_04.jpg'
'File_Ex_5.jpg' would be renamed to 'File_Ex_05.jpg'
'File_Ex_6.jpg' would be renamed to 'File_Ex_06.jpg'
'File_Ex_7.jpg' would be renamed to 'File_Ex_07.jpg'
'File_Ex_8.jpg' would be renamed to 'File_Ex_08.jpg'
'File_Ex_9.jpg' would be renamed to 'File_Ex_09.jpg'

(Remove flag -n to actually do the renaming.)

The argument to -e is a Perl search-and-replace expression; where _(\d\.) matches am underscore followed by a single digit and then a dot, and replaces the underscore with _0, thus inserting a leading zero. $1 is a back-reference to the group within parentheses, the digit and dot, and leaves it unchanged in the new file name.

dhag
  • 15,736
  • 4
  • 55
  • 65
2

While it can be done with prename command, I would like to provide a python alternative to that:

$ ls
File_Ex_1.jpg  File_Ex_2.jpg  File_Ex_3.jpg  File_Ex_4.jpg

$ ./pad_zeros.py *                                                                                                       

$ ls
File_Ex_01.jpg  File_Ex_02.jpg  File_Ex_03.jpg  File_Ex_04.jpg  pad_zeros.py*

The pad_zeros.py script is simple:

#!/usr/bin/env python
import sys,os,re

for file in sys.argv[1:]:
    if __file__ in file: continue
    words = re.split('_|\.',file)
    words[-2] = words[-2].zfill(2)
    new_name = "_".join(words[:-1]) + "." + words[-1]
    os.rename(file,new_name)

It takes arguments on command-line, iterates over them, and pads zeros using .zfill() command(in this particular case to two characters). Since we're operating on all files in the same folder, glob-star can be used.

  • This is the best answer adjustable to however many files need to be renamed. – mLstudent33 Nov 09 '19 at 18:23
  • Although I am getting an Anomalous backslash in string: '\g'. String constant might be missing an r prefix. (anomalous-backslash-in-string) error – mLstudent33 Nov 09 '19 at 18:39
  • could not run it so I chmod 755'd it. and now getting a boat load of errors:import-im6.q16: not authorizedsys' @ error/constitute.c/WriteImage/1037. import-im6.q16: not authorized os' @ error/constitute.c/WriteImage/1037. import-im6.q16: not authorizedre' @ error/constitute.c/WriteImage/1037. ./renameLeadZero.py: line 6: syntax error near unexpected token if' ./renameLeadZero.py: line 6: if file in file: continue' ` I am using a conda env so probably have to designate the interpreter on the shebang differently? – mLstudent33 Nov 09 '19 at 18:52
  • 1
    @mLstudent33 That's peculiar. The error in your second comment probably stems from a backslash in one of the filenames. Python regex thinks it's a special backslash escape. We probably can get around that by adding repr() around file in re.split() line, or string formatting ( https://stackoverflow.com/q/18707338/3701431 ) but we'll need to take into account whether it's Python3 or Python2.7 we have on the system - their string formatting is different just a bit. For the error in the third comment, I cannot confirm nor deny - never worked with Conda. – Sergiy Kolodyazhnyy Nov 09 '19 at 19:49
  • Thanks for the reply. – mLstudent33 Nov 09 '19 at 19:52
0

I have done with ls + awk + sh for the sake of challenge:

ls -1 | awk -F_ '{printf "%s%02d.jpg\n", "mv "$0" "$1"_"$2"_", $3}' | sh

-F_ ............... field separator %s ................ string to "mv "$0" "$1""$2"" %02d .............. our numeric field with leading zeros

To just see the command with no modifications remove the | sh

SergioAraujo
  • 459
  • 6
  • 8
  • In general, providing commands in plain text to sh, after processing unknown filenames, is vulnerable to code injection. Think of a file named 1 2;rm -rf ~;_Ex_1.jpg. – thanasisp Nov 15 '20 at 14:34
  • 1
    For sure thanasisp, but as I have said we can see the command before runing it simply by removing the | sh. – SergioAraujo Nov 15 '20 at 15:18
0

One answer above gives:

rename -e 's/\d+/sprintf("%02d",$&)/e' -- *.jpg

However, that gives incorrect results for me, since *.jpg sorts the files in strict numerical order. Running rename with the -n flag for a dry run gives me:

rename(9999.jpg, frames_0000039996.jpg) 
rename(999.jpg, frames_0000039997.jpg) 
rename(99.jpg, frames_0000039998.jpg) 
rename(9.jpg, frames_0000039999.jpg)

Correct results can be had with:

rename 's/.+/our $i; sprintf("frames_%010d.jpg", 0+$i++)/e' $(ls -1 | sort -V)

which gives

rename(9999.jpg, frames_0000039996.jpg)
rename(999.jpg, frames_0000039997.jpg)
rename(99.jpg, frames_0000039998.jpg)
rename(9.jpg, frames_0000039999.jpg)

with the -n flag.

Mr Squid
  • 101