Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass filehandle as reference between modules and subs in perl

I'm maintaining old Perl code and need to enable strict pragma in all modules. I have a problem in passing a file handle as a reference between modules and subs. We have a common module responsible for opening the log file which is passed as typeglob reference. In other modules, the run function first calls open_log() from the common module, then it passes this file handle to other subs.

Here I've written a simple test to simulate the situation.

#!/usr/bin/perl -w
use strict;

$::STATUS_OK = 0;
$::STATUS_NOT_OK = 1;

sub print_header {
  our $file_handle = @_;

  print { $$file_handle } "#### HEADER ####"; # reference passing fails
}

sub print_text {
  my ($file_handle, $text)= @_;

  print_header(\$file_handle);
  print { $$file_handle } $text;
}

sub open_file_handle {
  my ($file_handle, $path, $name) = @_;

  my $filename = $path."\\".$name;
  unless ( open ($$file_handle, ">".$filename)) {
    print STDERR "Failed to open file_handle $filename for writing.\n";
    return $::STATUS_NOT_OK;
  }
  print STDERR "File $filename was opened for writing successfully.\n";
  return $::STATUS_OK;
}

my $gpath = "C:\\Temp";
my $gname = "mylogfile.log";
my $gfile_handle;

if (open_file_handle(\$gfile_handle, $gpath, $gname) == $::STATUS_OK) {
  my $text = "BIG SUCCESS!!!\n";
  print_text(\$gfile_handle, $text);
  print STDERR $text;
} else {
  print STDERR "EPIC FAIL!!!!!!!!\n";
}

The Main function first calls open_file_handle and passes a file handle reference to the print_text function. If I comment out the row:

print_header(\$file_handle);

Everything works fine, but I need to pass the file handle reference to other functions from the print_text function, and this doesn't work.

I'm a Java developer and Perl's reference handling is not familiar to me. I don't want to change the open_log() sub to return a file handle (now it returns only status), since I have lots of modules and hundreds of code lines to go through to make this change in all places.

How can I fix my code to make it work?

like image 214
edufinn Avatar asked Dec 07 '25 10:12

edufinn


2 Answers

There are two types of filehandles in Perl. Lexical and global bareword filehandles:

open my $fh, '>', '/path/to/file' or die $!;
open FILEHANDLE, '>', '/path/to/file' or die $!;

You are dealing with the first, which is good. The second one is global and should not be used.

The file handles you have are lexical, and they are stored in a scalar variable. It's called scalar because it has a dollar sign $. These can be passed as arguments to subs.

foo($fh);

They can also be referenced. In that case, you get a scalar reference.

my $ref = \$fh;

Usually you reference stuff if you hand it over to a function so Perl does not make a copy of the data. Think of a reference like a pointer in C. It's only the memory location of the data (structure). The piece of data itself remains where it is.

Now, in your code you have references to these scalars. You can tell because it is dereferenced in the print statement by saying $$fh.

sub print_text {
  my ($file_handle, $text)= @_;

  print_header(\$file_handle);
  print { $$file_handle } $text;
}

So the $file_handle you get as a parameter (that's what the = @_ does) is actually a reference. You do not need to reference it again when you pass it to a function.

I guess you wrote the print_header yourself:

sub print_header {
  our $file_handle = @_;

  print { $$file_handle } "#### HEADER ####"; # reference passing fails
}

There are a few things here: - our is for globals. Do not use that. Use my instead. - Put parenthesis around the parameter assignment: my ($fh) = @_ - Since you pass over a reference to a reference to a scalar, you need to dereference twice: ${ ${ $file_handle } }

Of course the double-deref is weird. Get rid of it passing the variable $file_hanlde to print_header instead of a refence to it:

sub print_text {
  my ($file_handle, $text)= @_;

  print_header($file_handle); # <-- NO BACKSLASH HERE
  print { $$file_handle } $text;
}

That is all you need to to make it work.

In general, I would get rid of all the references to the $file_handle vars here. You don't need them. The lexical filehandle is already a reference to an IO::Handle object, but don't concern yourself with that right now, it is not important. Just remember:

  • use filehandles that have a $ up front
  • pass them without references and you do not need to worry about \ and ${} and stuff like that

For more info, see perlref and perlreftut.

like image 74
simbabque Avatar answered Dec 10 '25 01:12

simbabque


You are having difficulties because you added multiple extra level of references. Objects like lexical filehandles already are references.

If you have difficulties keeping track of what is a reference, you might want to use some kind of hungarian notation, like a _ref suffix.

In print_text, this would be:

sub print_text {
  my ($file_handle_ref, $text)= @_;

  print_header(\$file_handle_ref);
  print { $$file_handle_ref } $text;
}

And in print_header:

sub print_header {
  my ($file_handle_ref_ref) = @_; # don't use `our`, and assign to a lvalue list!

  print { $$$file_handle_ref_ref } "#### HEADER ####"; # double derefernence … urgh
}

A far superior solution is to pass the filehandle around directly, without references.

sub print_header {
  my ($file_handle) = @_;

  print {$file_handle} "#### HEADER ####"; # no reference, no cry
}

sub print_text {
  my ($file_handle, $text)= @_;

  print_header($file_handle);
  print {$file_handle} $text;
}

And in the main part:

my $gpath = "C:/Temp"; # forward slashes work too, as long as you are consistent
my $gname = "mylogfile.log";

if (open_file_handle(\my $gfile_handle, $gpath, $gname) == $::STATUS_OK) {
  my $text = "BIG SUCCESS!!!\n";
  print_text($gfile_handle, $text);
  ...
} else {
  ...
}
like image 39
amon Avatar answered Dec 10 '25 01:12

amon