Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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?

share|improve this question
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 Beam Feb 24 at 20:54
5  
Is the order of images important? If not, just rename the last image from imageN.jpg to image0.jpg and you are done. –  jnovacho Feb 25 at 9:57
    
@jnovacho, quite clever! But yes, the order of images is important. –  Wes Feb 25 at 20:24
    
thanks for accepting! –  bersch Mar 26 at 23:54
add comment

6 Answers

up vote 15 down vote accepted

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.

share|improve this answer
    
What does the "e" indicate? –  Wes Feb 24 at 22:02
3  
'e' means evaluate, the replace section will be evaluated. see also perlreref –  bersch Feb 24 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 Feb 24 at 22:20
    
I noticed this already, also there must not be a 0 in the sources. –  bersch Feb 24 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 Mar 3 at 21:32
show 1 more comment

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.

share|improve this answer
    
That is not neater. :) Solves the problem though, so +1. –  JMCF125 Feb 24 at 22:34
add comment

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

share|improve this answer
1  
Better to use -i or -n with mv, it will prevent overwriting in edge cases. –  Graeme Feb 24 at 22:36
    
wouldn't sort -V be better instead? –  user80551 Feb 25 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 Feb 25 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 Feb 25 at 20:08
add comment

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.

share|improve this answer
    
File already exists issues with this one. –  Wes Feb 24 at 22:01
add comment

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.

share|improve this answer
1  
Better to use -i or -n with mv, it will prevent overwriting in edge cases. –  Graeme Feb 24 at 22:37
    
Very good suggestion, I'll edit the answer –  brm Feb 25 at 7:01
add comment

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.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.