tags:

views:

238

answers:

6

This subroutine is passed an array of file handles to close, which it closes one by one using the foreach loop:

sub closef
{
    foreach(@_) {
        my $fh = shift;
        close $fh;
    }   
}

Where all can this simple subroutine be modified to make it better?

Should I use close shift instead of doing it in two lines?

+6  A: 
foreach(@_) {
    close $_;
}   

or

foreach my $fh (@_) {
    close $fh;
}   

or

close $_ foreach (@_);
David Dorward
+8  A: 
sub closef { close $_ for @_ }

But the close function does have a return value and you would usually do well to examine it. Therefore,

sub closef { map { close $_ } @_ }
Aristotle Pagaltzis
If you do check the value, you may also want/need to examine `$!` variable, so whilc this is a great improvement as far as error handling, this is not 100% sufficient.
DVK
+11  A: 

The most concise way is to use lexical filehandles which automatically close when they go out of scope:

sub firstline {
    open( my $in, shift ) && return scalar <$in>;
    # no close() required
}

See perldoc perlopentut.

eugene y
+1 for using lexical filehandles and letting them close themselves when their scope exits.
Dave Sherohman
Thanks @eugene, I did not know about these, will continue to explore.
Lazer
+2  A: 

Or you can also use map:

map { close $_ } @_;

With map you can even get an array of all results of close calls if you want to process it.

my @results = map { close $_ } @_;
Peer Stritzinger
+4  A: 

Your code isn't doing what you think. You say you want to close each filehandle passed to the routine, but you only close some of them. Consider the following example:

sub t{
        foreach(@_){
                my $tmp = shift;
                print $tmp;
        }
}

t(('a'...'d'));

This will output

ab

Despite the argument list being a, b, c and d. This is because on each iteration shift is being called. To get the kind of behavior you want use

sub t{
        foreach(@_){
                my $tmp = $_;
                print $tmp;
        }
}

t(('a'...'d'));

Or, better, don't make a copy of $_, just alias it:

sub t{
        foreach my $tmp (@_){
                print $tmp;
        }
}

t(('a'...'d'));
Sorpigal
@Sorpigal: thanks a lot for these examples. I had no idea this was happening.
Lazer
+4  A: 

There's an issue with the subroutine originally posted.

The shift function expects an array as an argument. The implicit variable inside the foreach is a scalar.

This means that $fh remains uninitialized. The reason why it probably goes unnoticed is because (as eugene y points out) lexical filehandles close themselves when they go out of scope.

Incidentally, use warnings; will notify about such things.

A more idiomatic approach would be to use a while loop:

sub closef {

    while ( my $fh = shift @_ ) {
        close $fh;
    }
}

For more compact notation, a for loop is the way to go:

sub closef { close for @_; }

The equivalent with a while loop is not so readable:

sub closef { close while $_ = shift }

Update

Michael Carman's comment below holds true here. The shift modifies the array being iterated over, which makes it an unsafe operation. This is, in fact, the problem.

Zaid
+1 for completeness
Sorpigal
There is a problem with the use of `shift`, but it's not the lack of an argument. It will operate on either `@_` or `@ARGV` depending on where it is used; it works on `@_` here. The problem is using it inside a `foreach`. From perlsyn: *`foreach` will get very confused if you add or remove elements within the loop body*.
Michael Carman
@Zaid: thanks for pointing this out!
Lazer