我正在维护旧的Perl代码,需要在所有模块中启用严格的pragma。我在传递文件句柄作为模块和subs之间的引用时遇到问题。我们有一个公共模块负责打开日志文件,该文件作为typeglob引用传递。在其他模块中,run函数首先从公共模块调用open_log(),然后将此文件句柄传递给其他subs。
在这里,我写了一个简单的测试来模拟情况。
#!/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";
}
Main函数首先调用open_file_handle
并将文件句柄引用传递给print_text
函数。如果我注释掉这一行:
print_header(\$file_handle);
一切正常,但我需要将文件句柄引用传递给print_text
函数中的其他函数,这不起作用。
我是Java开发人员,Perl的参考处理对我来说并不熟悉。我不想更改open_log()
子以返回文件句柄(现在它只返回状态),因为我有很多模块和数百个代码行可以在所有地方进行此更改。
如何修复代码以使其正常工作?
答案 0 :(得分:8)
Perl中有两种类型的文件句柄。词法和全局裸字文件句柄:
open my $fh, '>', '/path/to/file' or die $!;
open FILEHANDLE, '>', '/path/to/file' or die $!;
你正在处理第一个问题,这很好。第二个是全球性的,不应该使用。
您拥有的文件句柄是词法,并且它们存储在标量变量中。它被称为标量,因为它有一个美元符号$
。这些可以作为参数传递给subs。
foo($fh);
它们也可以被引用。在这种情况下,您会得到一个标量参考。
my $ref = \$fh;
通常你引用东西,如果你把它交给一个函数,所以Perl不会复制数据。可以想象像C中的指针一样的引用。它只是数据(结构)的内存位置。这段数据本身仍然存在。
现在,在您的代码中,您可以引用这些标量。您可以告诉,因为print
语句中的$$fh
语句已取消引用它。
sub print_text {
my ($file_handle, $text)= @_;
print_header(\$file_handle);
print { $$file_handle } $text;
}
因此,作为参数得到的$file_handle
(= @_
所做的)实际上是一个参考。将它传递给函数时,不需要再次引用它。
我猜你自己写了print_header
:
sub print_header {
our $file_handle = @_;
print { $$file_handle } "#### HEADER ####"; # reference passing fails
}
这里有一些事情:
- our
适用于全局变量。不要使用它。请改用my
。
- 在参数赋值周围加上括号:my ($fh) = @_
- 由于您将对引用的引用传递给标量,因此需要取消引用两次:${ ${ $file_handle } }
当然,双击是很奇怪的。摆脱它将变量$file_hanlde
传递给print_header
而不是反对它:
sub print_text {
my ($file_handle, $text)= @_;
print_header($file_handle); # <-- NO BACKSLASH HERE
print { $$file_handle } $text;
}
这就是你需要做的所有工作。
一般情况下,我会在这里删除对$file_handle
变量的所有引用。你不需要它们。词法文件句柄已经是reference to an IO::Handle object,但现在不关心这一点,这并不重要。记住:
$
的文件句柄\
和${}
以及类似的内容有关详细信息,请参阅perlref和perlreftut。
答案 1 :(得分:2)
您遇到了困难,因为您添加了多个额外级别的引用。像词法文件句柄这样的对象已经是引用。
如果您难以跟踪什么是引用,则可能需要使用某种匈牙利语表示法,例如_ref
后缀。
在print_text
中,这将是:
sub print_text {
my ($file_handle_ref, $text)= @_;
print_header(\$file_handle_ref);
print { $$file_handle_ref } $text;
}
在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
}
一个更好的解决方案是直接传递文件句柄,而不引用。
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;
}
主要部分:
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 {
...
}
答案 2 :(得分:0)
参考运算符是“\”(反斜杠)
任何包括数组,哈希甚至子例程的东西都可以被引用
向后计数的第5行
print_text(\$gfile_handle, $text);
您将引用的变量\$gfile_handle
传递给子例程print_text
sub print_text {
my ($file_handle, $text)= @_;
print_header(\$file_handle);
print { $$file_handle } $text;
}
在这个子例程中,$file_handle
已经是参考文献
然后再次引用它并将其传递给子例程print_header
所以,你可以通过推迟参考操作员第5行向后计数来解决这个问题,如下所示:
print_text($gfile_handle, $text);
再试一次: - )