4

I was testing/trying out this Perl one-liner, and I'm trying to figure out what happened to the files. I don't see the files anymore. Did I delete them or what went wrong?

Example of file names listed (original):

IMG_0178.JPG
IMG_0182.JPG
IMG_0183.JPG
IMG_0184.JPG
IMG_0186.JPG

I wanted to simply change the file extension to lowercase (.jpg):

perl -e'while(<*.JPG>) { s/JPG$/jpg/; rename <*.jpg>, $_ }'
Community
  • 1
  • 1
cjd143SD
  • 859
  • 7
  • 17
  • 28

2 Answers2

9

Don't use rename with a glob. Use scalars. Try to assign the file name to a new variable before the substitution and rename the old name to the modified one, like this:

perl -e'while(<*.JPG>) { ($new = $_) =~ s/JPG$/jpg/; rename $_, $new }'

Check output with ls -1:

IMG_0178.jpg                                                                                                     
IMG_0182.jpg                                                                                                
IMG_0183.jpg                                                                                              
IMG_0184.jpg                                                                                               
IMG_0186.jpg
Birei
  • 35,723
  • 2
  • 77
  • 82
  • thanks. so the glob that I was using was `rename <*.jpg>`? In my example, were the files deleted? Thanks again – cjd143SD Jul 06 '12 at 20:57
  • @cjd143SD: Your command didn't delete anything in my test. It left files with same name, but I guess that `<*.jpg>` doesn't match anything. I tried with `<*.JPG>` instead and worked, but as I told you before, I prefer to use `rename` with scalars. – Birei Jul 06 '12 at 21:07
4

Bizarrely your code should do what you wanted.

A file glob like <*.JPG> in scalar context will return the next file that matches the pattern, and since both while and rename apply scalar context, the two globs return the same value at each iteration.

while (<*.JPG>) {
    s/JPG$/jpg/;
    rename <*.jpg>, $_;
}

In the first iteration of the loop $_ is set to IMG_0178.JPG by the while, and the substitution sets the file type to lower case.

Then in the rename <*.jpg> is executed in scalar context and again returns IMG_0178.JPG - the first file in the same list because Windows file names are case-insensitive.

So finally the rename performs rename 'IMG_0178.JPG', 'IMG_0178.jpg' as required.

Rewriting rename like this shows this clearly

sub ren($$) {
  print "$_[0] -> $_[1]\n";
}

while (my $file = <*.JPG>) {
  $file =~ s/JPG$/jpg/;
  ren <*.JPG>, $file;
}

output

IMG_0178.JPG -> IMG_0178.jpg
IMG_0182.JPG -> IMG_0182.jpg
IMG_0183.JPG -> IMG_0183.jpg
IMG_0184.JPG -> IMG_0184.jpg
IMG_0186.JPG -> IMG_0186.jpg

So you are lucky, and your files should have been renamed as you wanted.

But don't do this. In particular you should run the program with a print statement in place of any critical operations so that you can see what is going to happen.

This would be better as id more clearly does what is intended

perl -e '($f = $_) =~ s/JPG$/jpg/i and rename $_, $f while <*.JPG>'
Borodin
  • 126,100
  • 9
  • 70
  • 144