2

我正在维护旧的 Perl 代码,需要在所有模块中启用严格的编译指示。我在传递文件句柄作为模块和潜艇之间的引用时遇到问题。我们有一个公共模块负责打开作为 typeglob 引用传递的日志文件。在其他模块中,run 函数首先从 common 模块调用 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()sub 以返回文件句柄(现在它只返回状态),因为我有很多模块和数百行代码行要在所有地方进行此更改。

如何修复我的代码以使其正常工作?

4

3 回答 3

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 } }

当然,双重deref很奇怪。摆脱它传递变量$file_hanldeprint_header不是引用它:

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

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

这就是让它工作所需的一切。

一般来说,我会在$file_handle这里去掉所有对变量的引用。你不需要它们。词法文件句柄已经是对 IO::Handle 对象的引用,但现在不要关心它,这并不重要。只要记住:

  • 使用预先设置好的文件$句柄
  • 通过他们没有参考,你不需要担心\${}类似的东西

有关详细信息,请参阅perlrefperlreftut

于 2013-05-14T09:29:10.700 回答
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 {
  ...
}
于 2013-05-14T09:26:30.747 回答
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);
然后再试一次 :-)

于 2013-05-14T09:27:01.767 回答