20

I have a directory which contains image files with names like

image1.jpg 
image2.jpg 
image3.jpg
...

Unfortunately, the image names must be zero based, so image1.jpg should be image0.jpg, image2.jpg should be image1.jpg and so on.

I can write a script to generate mv commands like these, put them in a shell script, and then execute them -

mv image1.jpg image0.jpg
mv image2.jpg image1.jpg
mv image3.jpg image2.jpg
...

But I suppose there is a neater way to do it in Unix. So what is it?

3
  • 6
    That's actually the simplest way to do it, as long as there are no gaps. (for i in $(seq 0 100); do mv image$[i+1].jpg image$i.jpg; done)
    – Ricky
    Commented Feb 24, 2014 at 20:54
  • 6
    Is the order of images important? If not, just rename the last image from imageN.jpg to image0.jpg and you are done.
    – jnovacho
    Commented Feb 25, 2014 at 9:57
  • @jnovacho, quite clever! But yes, the order of images is important.
    – Wes
    Commented Feb 25, 2014 at 20:24

6 Answers 6

23

The good old perl rename:

rename 's/(\d+)(\.jpg)/($1-1).$2/e' *

[Remarks]

Image numbers should be greater than 0.

In case images are greater than 9 and have not leading 0s, use $(ls -v1 *) to avoid clobbering. Proposed by @arielf and noticed by @Graeme.

When in doubt use also -v for verbose and -n for no-action.

6
  • What does the "e" indicate?
    – Wes
    Commented Feb 24, 2014 at 22:02
  • 3
    'e' means evaluate, the replace section will be evaluated. see also perlreref
    – user55518
    Commented Feb 24, 2014 at 22:05
  • 6
    Files should be passed in ascending order to avoid any clobber. This will work fine only if there are less than 10 files (or if numbers have leading zeros).
    – Graeme
    Commented Feb 24, 2014 at 22:20
  • I noticed this already, also there must not be a 0 in the sources.
    – user55518
    Commented Feb 24, 2014 at 22:22
  • 2
    replace the * at the end with `ls -v1 *` and you'll be safer from clobbering. It'll go from the lowest number to highest.
    – arielf
    Commented Mar 3, 2014 at 21:32
6

You can pipe the generated mv commands to bash. So you don't have to copy them in a script and execute that. See:

command_that_generates_mv_commands | bash

And everything will be executed that is piped to bash.

1
  • That is not neater. :) Solves the problem though, so +1.
    – JMCF125
    Commented Feb 24, 2014 at 22:34
4

You can iterate on ls output, this one works for your example:

i=0 
for file in $(ls *.jpg | sort) ; do 
     mv $file $(echo $file | sed 's/[0-9]*.jpg$/'${i}'.jpg/')
     i=$((++i))
done

You must be on the same path of your files

5
  • 1
    Better to use -i or -n with mv, it will prevent overwriting in edge cases.
    – Graeme
    Commented Feb 24, 2014 at 22:36
  • wouldn't sort -V be better instead?
    – user80551
    Commented Feb 25, 2014 at 15:48
  • sort won't work, it will show image10.jpg before image1.jpg which might be an issue. You need sort -nk 1.8 as in @Graeme's answer.
    – terdon
    Commented Feb 25, 2014 at 20:00
  • @user80551, sort -V is a good idea, will add it to my answer. There could be other edge cases though, just generally good advice to add these kind of options with questions like this in case someone copies and ends up doing some damage.
    – Graeme
    Commented Feb 25, 2014 at 20:08
  • Careful! This solution only works if your numbers are all consecutive (or rather, if you want them to become consecutive, starting from 0). Commented Sep 24, 2023 at 18:08
4

The following seems to work for anything that fits the pattern imageNUMBER.jpg. I've placed echo before the mv command to first show what the command would do; to actually perform the renaming, just remove the echo

for i in `ls image*.jpg|sort -V` ; do 
    x=`echo $i|sed -e "s/image\(.*\).jpg/\1/"`
    y=$(( $x - 1 ))
    echo mv -i $i image$y.jpg
done

In the first line, the ls image*.jpg|sort -V will cause the JPG files to be listed with ascending numbers in the filename. The x= line extracts the number from the filename. The y= line then decrements the number by one. The input filename and the y number are then used in the mv command, where the -i flag will notify you before overwriting a file.

For my own small test, this produced the output:

mv -i image1.jpg image0.jpg
mv -i image2.jpg image1.jpg
mv -i image123.jpg image122.jpg

Personally, I'd suggest renaming to a more different filename, since now the order in which the files are processed can make a big difference.

3
  • 1
    Better to use -i or -n with mv, it will prevent overwriting in edge cases.
    – Graeme
    Commented Feb 24, 2014 at 22:37
  • Very good suggestion, I'll edit the answer
    – brm
    Commented Feb 25, 2014 at 7:01
  • In macOS, you should use sort -n, the -V option does't exist. Commented Jul 14, 2016 at 11:15
4

Using the perl script prename, which is symlinked to rename on Debian based distros, also needs GNU find/sort. The files are put in ascending order to prevent any overwriting.

find . -regex '\./image[0-9]+\.jpg' -print0 | sort -zV |
  xargs -0 rename -n 's/(\d+)\.jpg$/@{[$1-1]}.jpg/'

Remove the -n once you are sure it does what you want. Will warn about files already existing before doing so. However as long as it shows the files being renamed in ascending order, there will be no conflicts when run for real.

1
  • File already exists issues with this one.
    – Wes
    Commented Feb 24, 2014 at 22:01
4

With zsh:

autoload zmv # (in ~/.zshrc)
zmv -Qf -n 'image(<->).jpg(n)' 'image$(($1-1)).jpg'

(remove -n when happy).

(n) is to sort the list numerically so image9.jpg is renamed before image10.jpg.

You must log in to answer this question.

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