Photius has asked for the wisdom of the Perl Monks concerning the following question:

I am using ActiveState Perl 5.8.8 in Windows XP and Windows Server 2003. I want to create a subroutine to close and optionally unlink one or more open files. If I understand correctly, close takes a file handle and unlink takes a filename or glob (but not a handle). So the first challenge is that I really don't want to have to send both the file handles *and* filenames to the subroutine. But apparently I'm also doing something more basically stupid, because my subroutine does not succeed in even closing the files with the handles I'm sending it (which were opened as $fh variables). 1) What am I doing wrong with closing the files using the passed handles? 2) Any suggestions on how a single subroutine can both close and unlink a file without having to pass it both the file handle and filename?
#!C:/Perl/bin/perl /w # CloseUnlinkTest.pl use strict; my $file1 = 'file1.txt'; my $file2 = 'file2.txt'; my $file3 = 'file3.txt'; open(my $handle1, ">", $file1) or &quit({}, "Cannot open $file1: $!"); + # Quit, dont need to close any files open(my $handle2, ">", $file2) or &quit({$handle1=>0}, "Cannot open $f +ile2: $!"); # Quit, close $file1 open(my $handle3, ">", $file3) or &quit({$handle1=>0, $handle2=>0}, "C +annot open $file3: $!"); # Quit, close $file1 and file2 # Do stuff with the files ... &quit({$handle1=>1, $handle2=>1, $handle3=>1}, "CloseUnlinkTest.pl fin +ished"); # Quit, close and unlink all 3 files sub quit { my ($filesToClose, $message) = @_; + # Get calling parameters my %filesToClose = %$filesToClose; + # Dereference $filesToClose into hash %filesToClose print "\%filesToClose = @{[%filesToClose]}\n"; + # Debug to show hash contents print "message = $message\n"; + while (my ($file, $unlink) = each %filesToClose) { + # Loop thru the files to be closed print "closing $file with $unlink\n"; + # Debug to show the key and value of this hash entry close($file) or print "Error closing $file: $!\n"; + # Close this file unlink($file) or print "Error unlinking $$file: $!\n" if $unlink; + # Unlink this file if $unlink is not zero } exit; }
I would greatly appreaciate any direction the wise monks can provide. Thanks!

Replies are listed 'Best First'.
Re: Trouble Passing File Handles
by johngg (Canon) on Jun 16, 2010 at 23:04 UTC

    Rather than calling open directly it would probably be better to have a routine for opening files as well as one for closing them. It would maintain a HoH, %openFiles perhaps, keyed by file name or stringified handle, that would have a key/value pair added when a file was opened. The value would be a hash reference with key/value pairs for file name, file handle and a flag for whether or not to unlink. The HoH would be iterated over by the clean-up routine to close and optionally unlink any open files. I think this would be preferable to your scheme, where you have a different number of arguments in calls to quit() depending on how far you have reached in the script. By the way, use quit( ... ) rather than &quit( ... ) unless you want the specific behaviour that the ampersand dictates.

    I hope these ramblings are helpful. I'm sorry I haven't given a code example but it's way past my bedtime!

    Cheers,

    JohnGG

      Great ideas! Folks around the monastery are always helpful and inspire my creativity. Much appreciated. -- photius
Re: Trouble Passing File Handles
by coldguy (Sexton) on Jun 16, 2010 at 22:34 UTC

    Looks to me like when you're passing your handles as hash keys, they end up getting turned into strings and lose their magic. Don't use handles (or objects for that matter) as hash keys, pass it another way.

      Thanks for straightening me out on that! You are absolutely right. Duh!!! -- photius
Re: Trouble Passing File Handles
by derby (Abbot) on Jun 17, 2010 at 12:25 UTC

    coldguy was right, you cannot use a reference as a hash key. Also, I don't think you can use unlink on a filehandle ... just a filename. If you want to stay the same path, I would just expand your structure a bit:

    #!/usr/local/bin/perl use strict; my $files = [ { name => 'file1.txt', fh => undef, unlink => 1 }, { name => 'file2.txt', fh => undef, unlink => 1 }, { name => 'file3.txt', fh => undef, unlink => 1 }, ]; foreach my $file ( @$files ) { open( $file->{fh}, '>', $file->{name} ) or quit( $files, "Cannot open $file->{name}: $!" ); } quit( $files, "CloseUnlinkTest.pl finished" ); sub quit { my( $files, $message ) = @_; foreach my $file ( @$files ) { close( $file->{fh} ) if $file->{fh}; unlink( $file->{name} ) if $file->{unlink}; } }

    -derby
      Thanks a 10^6 !!! This will work perfectly. I like your clean elegant solution. I should wander the monastery halls more often! I always learn new things. -- photius
Re: Trouble Passing File Handles
by zek152 (Pilgrim) on Jun 17, 2010 at 14:11 UTC

    First of all you don't have to pass filehandles. You can use them like global variables. However, if you want to treat them like variables you could create an array of filehandles and pass that array. Or as you mentioned you wished not to send both the filename and filehandle the array could be global.

    my @fhandle_array; for $filename (@filenames) { local *FILE; #so that FILE is limited in scope open(FILE, "$filename") or die "could not open file\n"; #push the filehandle into your filehandle array push(@fhandle_array, *FILE); }

    To close the files simply either pass the array or make it global and use code similar to:

    sub close_files() #args: none. uses global array: fhandle_array { my $handle; foreach $handle (@fhandle_array) { close $handle; } }