views:

915

answers:

5

I originally had a set of images of the form image_001.jpg, image_002.jpg, ...

I went through them and removed several. Now I'd like to rename the leftover files back to image_001.jpg, image_002.jpg, ...

Is there a Linux command that will do this neatly? I'm familiar with rename but can't see anything to order file names like this. I'm thinking that since ls *.jpg lists the files in order (with gaps), the solution would be to pass the output of that into a bash loop or something?

A: 

This does the reverse of what you are asking (taking files of the form *.jpg.001 and converting them to *.001.jpg), but can easily be modified for your purpose:

for file in * 

do

if [[ "$file" =~ "(.*)\.([[:alpha:]]+)\.([[:digit:]]{3,})$" ]]

then

mv "${BASH_REMATCH[0]}" "${BASH_REMATCH[1]}.${BASH_REMATCH[3]}.${BASH_REMATCH[2]}"

fi

done
ennuikiller
+5  A: 

A simple loop (test with echo, execute with mv):

I=1
for F in *; do
  echo "$F" `printf image_%03d.jpg $I`
  #mv "$F" `printf image_%03d.jpg $I` 2>/dev/null || true
  I=$((I + 1))
done

(I added 2>/dev/null || true to suppress warnings about identical source and target files. If this is not to your liking, go with Matthew Flaschen's answer.)

Stephan202
This will work..
jim
This doesn't work. Most of the images will still have their original names, so the mv will fail.
Matthew Flaschen
@Matthew: good spot. Now changed the code so that all files are moved to a temporary directory.
Stephan202
To be clear, it will work on the other images, but will produce spurious errors that can break e.g. Makefiles.
Matthew Flaschen
@Matthew: on second thought, failing is not a problem. I'll revert the code.
Stephan202
Keep in mind, mv fails even with the /dev/null, so make (and similar) still break.
Matthew Flaschen
Doh. I should go to bed. Anyway, should be fixed now.
Stephan202
Nice job. :)
Matthew Flaschen
+3  A: 

If I understand right, you have e.g. image_001.jpg, image_003.jpg, image_005.jpg, and you want to rename to image_001.jpg, image_002.jpg, image_003.jpg.

EDIT: This is modified to put the temp file in the current directory. As Stephan202 noted, this can make a significant difference if temp is on a different filesystem. To avoid hitting the temp file in the loop, it now goes through image*

i=1; temp=$(mktemp -p .); for file in image*
do
mv "$file" $temp;
mv $temp $(printf "image_%0.3d.jpg" $i)
i=$((i + 1))
done
Matthew Flaschen
Nice (as long as mktemp creates a file on the same volume, otherwise moving to a temporary subdirectory is faster)
Stephan202
Good point. This can be addressed with mktemp -p .
Matthew Flaschen
I'm going to mark this as the answer, but I didn't need to use a temp directory: my images actually started at 002 (with gaps), therefore there couldn't be any conflicts. 002 is renamed 001, 005->002, 006->003 and so on.
DisgruntledGoat
For other people's reference, my final command was: i=0; for file in *.jpg; do mv "$file" $(printf "image_%0.3d.jpg" $i); i=$((i+1)); done
DisgruntledGoat
A: 

I was going to suggest something like the above using a for loop, an iterator, cut -f1 -d "_", then mv i i.iterator. It looks like it's already covered other ways, though.

jess
+1  A: 

Some good answers here already; but some rely on hiding errors which is not a good idea (that assumes mv will only error because of a condition that is expected - what about all the other reaons mv might error?).

Moreover, it can be done a little shorter and should be better quoted:

for file in *; do
    printf -vsequenceImage 'image_%03d.jpg' "$((++i))"
    [[ -e $sequenceImage ]] || \
        mv "$file" "$sequenceImage"
done

Also note that you shouldn't capitalize your variables in bash scripts.

lhunath
With recent versions of bash you can use:printf -vsequenceImage 'image_%03d.jpg' $((++i))
radoulov
Actually, you're manipulating the i variable in a subshell, so in your example it does not get incremented.
radoulov
radoulov: well spotted, thanks. I recon I should test what I write.
lhunath