Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fallback Open File Perl

Tags:

scope

perl

I was trying to write a program where perl opens one file, but falls back to another if that file does not exist or cannot be opened for some reason. The relevant line was:

open(my $fh,"<","/path/to/file") or open (my $fh,"<","/path/to/alternate/file") or die

Eventually, I figured out that:

open(my $fh,"<","/path/to/file") or open ($fh,"<","/path/to/alternate/file") or die

worked. What is the difference between these two statements, why doesn't the first work, and is the second the right way to do this, or are there still some problems with it?

Edit: If it matters, I'm using perl 5.12, and the first fails in the case that "/path/to/file" exists. My inclination is that the second open should not run if the first open is successful, so why is $fh being overwritten by the second?

like image 358
Chris Avatar asked Dec 07 '16 01:12

Chris


2 Answers

my declares a variable. If you use it twice with the same name in the same scope, later mentions of it will be the second one, not the first. Your code will trigger a "my" variable ... masks earlier declaration in the same statement warning (if you enable warnings as you should.) So if the first open succeeds, it sets a $fh variable that isn't accessible later, and the second variable is left in an undocumented, undefined state, because its declaration wasn't actually executed. (See the "Here be dragons" warning in perldoc perlsyn, and realize that A or B is equivalent to B unless A.)

Your "working" code is also broken; while my returns the newly declared variable, which can be then set, the scope of a lexical (where later mentions of it find the variable) doesn't actually begin until the following statement. So your first $fh is the lexical that will be accessed on later lines, but the second is actually a global variable (or an error, if you are using strict as you should).

Correct code is:

my $fh;
open $fh, ... or open $fh, ...;
like image 87
ysth Avatar answered Nov 16 '22 21:11

ysth


Others have said why the existing code doesn't work, but have also offered versions that have race conditions: the state of the file might change between when you checked it and when you opened it. It's fairly benign in your case, but it can produce subtle bugs and security holes. In general, you check if you can open a file by trying to open a file.

Here's a more general way which scales to multiple files, lets you know which file opened, and contains no race conditions.

use Carp;

sub try_open {
    my @files = @_;

    for my $file (@files) {
        if( open my $fh, "<", $file ) {
            return { fh => $fh, file => $file };
        }
    }

    croak "Can't open any of @files";
}
like image 37
Schwern Avatar answered Nov 16 '22 20:11

Schwern