1

I am using cygwin. What this script does is load the iphone pics that I have loaded into a directory on the desktop. It opens it up in image viewer, to let me take a look at the picture.

system("cygstart $dirname/$oldfile") ;

Then it gives me the option to rename the picture. It is throwing errors though, and not renaming the picture.

Use of uninitialized value $oldfile in concatenation (.) or string at ./rename_image.pl line 29, <STDIN> line 6.

oldfile is a global varaible, the functions should see the variable.

#!/usr/bin/perl
#
use strict ;
use warnings ;

my $oldfile;
my $new_name;
my $dirname = "/cygdrive/c/Users/walt/Desktop/iphonepics/bunk_box/";
opendir(DIR, $dirname) or die "Cannot open dir: $!";

my @files = readdir(DIR);
foreach $oldfile (@files) {
        system("cygstart $dirname/$oldfile") ;
        print "Do you want to rename $oldfile ? ";
        my $input = <STDIN> ;
        chomp $input ;
        if($input =~ m/^[y]$/i) {
                rename_file() ;
        } else {
        my $doo = 1 ;
        }
}

sub rename_file {
        use File::Copy qw(move) ;
        print "New name:\n" ;
        my $new_name = <STDIN> ;
        chomp $new_name ;
        move "$dirname/$oldfile", "$dirname/$new_name";
        return ;
}
zdim
  • 64,580
  • 5
  • 52
  • 81
capser
  • 2,442
  • 5
  • 42
  • 74
  • Are we seeing 100% of the code? There is no possible way for rename_file to have been executed except from within the `foreach` loop? – DavidO Sep 25 '17 at 06:12
  • This is unrelated to the warning you are seeing, but have you noticed that your paths will look like this? `/cygdrive/c/Users/walt/Desktop/iphonepics/bunk_box//foobar` (In other words, you're concatenating a string that ends with / to the filename, using another / in your concatenation. – DavidO Sep 25 '17 at 06:15
  • Yes this is the code, and no, it does not rename any files. It so far does not work as planned. Also, no I did not notice the extra slash in the $dirname variable. what is the proper, accepted way to delineate an absolute path as a variable - ending in a slash or open (with the slash being assumed) – capser Sep 25 '17 at 17:31
  • All I want to do is rename some pictures - the cygstart is just do I can see what picture is loaded into the array, the visual helps me figure uot a name for it. Most of the Pictures are named IMG_* by default. – capser Sep 25 '17 at 17:32
  • Doubling a slash should have absolutely no effect (but I don't use Cygwin much, please test). Whether you include it in the directory or when you compose a path is a matter of choice of the convention you want: either `$dir = '.../';` and `$file = $dir . $name;` OR `$dir = '...'` (no slash) and `$file = "$dir/$name"`. But you might as well use both, to be on the safe side. – zdim Sep 25 '17 at 17:38
  • `use File::Spec::Functions qw(catfile); my $full_path = catfile($path, $filename);` ...this is a core module and should eliminate things like duplicated slashes. In your particular use case I'm not sure duplicated slashes matter, but they're sloppy. – DavidO Sep 26 '17 at 18:05

1 Answers1

1

The foreach $oldfile (@files) does not assign values to the my $oldfile declared at the top. That one is a lexical variable, not global, and in such a case a new lexical $oldfile is created for the loop's scope. This is a particular property of foreach alone. See this post for details.

Since the $oldfile in foreach is a lexical in a dynamic scope it is invisible to the sub. But the sub sees my $oldfile declared for the static scope of the file. From Private Variables via my()

... lexical variables declared with my are totally hidden from the outside world, including any called subroutines. This is true if it's the same subroutine called from itself or elsewhere--every call gets its own copy.

This doesn't mean that a my variable declared in a statically enclosing lexical scope would be invisible. Only dynamic scopes are cut off. For example, ...

That $oldfile at the top, seen by the sub, stays as it was before the loop, uninitialized.

Altogether, a simple "fix" to get the expected behavior is to make $oldfile on top a global variable, so to replace my $oldfile with our $oldfile.  

However, why rely on global variables? Why not pass to functions what they need

foreach my $oldfile (@files) {
    ...
    if (...) {
        rename_file($dirname, $oldfile);
    ...
}

sub rename_file {
    my ($dirname, $oldfile) = @_;
    ...
}

You are nicely using strict already and declaring everything else. Then you don't need, and should not have, the file-wide lexical my $oldfile; declared on top.

When you declare a variable in a sub, it is shielded from variables with the same name outside the sub's scope. So here you know what $oldfile is – it is exactly what you passed to the function.

That way your sub also has a well defined interface and does not depend on arbitrary values in the surrounding code. This is crucial for clear delineation of scope and for modularity.

More generally, using a global variable while strict is in place and other things declared is like planting a mine for the maintenance programmer, or for yourself the proverbial six months later.

I suggest to also read Coping with Scoping by Mark-Jason Dominus.


I don't recommend this unless there is a very specific and solid reason for a global-like our variable.

zdim
  • 64,580
  • 5
  • 52
  • 81
  • This is all sound advice, but if the code we're seeing is 100% of the code the OP is using, the warning should not be possible, as it would mean that `readdir` populated `@files` with an undef entry, and furthermore, that the user of the script managed to still say "Y" to the question of whether to move the file, when the filename didn't show up in the screen output. Given it's unlikely that we're seeing the code that produced this warning, it's hard to come to the conclusion that this answer actually solves the OP's question. – DavidO Sep 25 '17 at 06:21
  • 1
    @DavidO I added the explanation for the warning, thanks for prompting it. As for the "impossible" -- it isn't, it's just how it has to be. Try it, give some value to `my $oldfile` when it is declared. – zdim Sep 25 '17 at 06:24
  • Yes - this is the code in its entirety - all it does is open up the photo in photoviewer. I look at the photo, which new photos taken with my iphone typically are called IMG_1234.jpg. If I like it I hit yes and rename the photo. Anything left IMG_* will be removed from the directory. – capser Sep 25 '17 at 17:28
  • @capser Thank you for confirming it -- I thought so and answered your question about the particular error. Did you mean to respond to DavidO? (If so tag your comment with AT-DavidO) – zdim Sep 25 '17 at 17:31
  • @capser Please let me know if I need to explain things more in this answer. A note: while it is enough to replace `my $oldfile` with `our $oldfile`, as explained, I do not recommend that -- better pass things to functions, as shown. (Unless you have a lot of code that relies on globals...) – zdim Sep 25 '17 at 17:34
  • @zdim - No thank you very much I did not understand why the sub did not see the my variable, like I did not understand why I initialized the variable with strict, and then getting uninitialized errors. This was actually a very good lesson. – capser Sep 25 '17 at 17:43
  • 1
    @capser Yes, in my opinion this is a good question, that is important as a good lesson. I hope that I explained it well enough, if not please let me know what is unclear in my answer and I'll rephrase things. Thank you for attribution. – zdim Sep 25 '17 at 17:46