【问题标题】:Accessing from modules a filehandle defined in main program从模块访问主程序中定义的文件句柄
【发布时间】:2017-11-11 06:48:09
【问题描述】:

我在 Perl 中有以下关于文件处理程序访问的查询。

考虑以下描述确切场景的代码 sn-p。

Main.pl

#!/usr/bin/perl -w
use warnings;
use strict;
use strict 'refs';

use File::Basename;
use Fcntl ':flock';

use feature qw/say switch/;

use File::Spec::Functions;
use File::Find;

require( "/home/rxa3kor/Mastering_Perl/sample.pm" );

our $LOGFILE = "sample";
open( LOGFILE, ">$LOGFILE" ) or die "__ERROR: can't open file\n'", $LOGFILE, "'!\n";
flock( LOGFILE, LOCK_EX );
print LOGFILE ( "Tool Start\n" );

&sample::func();

flock( LOGFILE, LOCK_UN );
close( LOGFILE );

sample.pm

#!/usr/bin/perl -w
package sample;

sub func() {
    print $main::LOGFILE ( "Printing in subroutine\n" );
}

当我执行上述代码 sn-p 时,出现以下错误。

print() 在 /home/rxa3kor/Mastering_Perl/sample.pm 第 6 行的未打开文件句柄 Mastering 上。

这个错误是因为文件句柄LOGFILEsample.pm 模块下不可见。

如何实现这个概念?

我想在Main.pl 中打开一个文件,我需要这个文件句柄可以在不同的 Perl 模块中访问。

【问题讨论】:

  • 我认为这不是您使用的代码。此代码无法编译。您在use File::Find 行的末尾缺少一个分号。一旦我解决了这个问题,我就会遇到另一个问题,因为您没有在主程序中加载sample.pm。请不要浪费我们的时间在我们必须修复此类简单错误的地方发布示例代码。
  • 另外两个错误。 sample.pm 不返回真值。此示例代码和您引用的错误消息之间的文件名不同。
  • 基本上我想知道我们是否可以在 main.pl 下打开一个文件,我需要这个文件句柄可以在不同的 Perl 模块中访问。
  • 这和你问的问题无关,但是你认为当你以'>'模式打开你的日志文件,然后发现你无法获得排他锁时会发生什么因为别人把它锁了?
  • 提示:Clobber 输出模式会在你获得锁之前破坏输出文件。这意味着如果其他人已经用锁打开了文件,那么您只是破坏了他们。

标签: perl module filehandle


【解决方案1】:

您看到此错误的原因是$main::LOGFILE 引用了包含文件名sample 的标量变量$LOGFILE。文件句柄LOGFILE 是一个完全不同的变量。在这里,我们看到了使用相同名称的两个不同类型(标量与文件句柄)变量的危险。

Bareword 文件句柄(大写字母,没有附加符号,您正在使用的类型)是有点奇怪的变量。他们不需要印记,所以你不应该使用印记。所以最简单的解决方法是删除$

sub func()
{
  print main::LOGFILE ("Printing in subroutine\n");
}

但是像这样使用全局变量是一个糟糕的主意。它会很快导致您的代码变成无法维护的混乱。

最好使用 lexical 文件句柄并将其传递到您的子例程中。

our $LOGFILE="sample";
open( my $log_fh, ">$LOGFILE" ) or die "__ERROR: can't open file\n'",$LOGFILE,"'!\n";
flock( $log_fh, LOCK_EX );
print $log_fh ("Tool Start\n");
&sample::func($log_fh);
flock( $log_fh, LOCK_UN );
close( $log_fh );

sample.pm:

sub func
{
  my ($fh) = @_;
  print $fh ("Printing in subroutine\n");
}

请注意,我现在将参数传递给func()。我删除了原型,说它不带参数(尽管您使用 & 调用它的事实关闭了参数检查!)

其他几点。

  • 您不需要-wuse warnings。删除-w
  • 您不需要use strictuse strict 'refs'。删除后者。
  • 全小写名称的模块保留用于称为 pragmas 的特殊 Perl 功能。不要这样命名你的模块。
  • $LOGFILE 不需要成为包变量(使用our 定义)。只需将其设为词法(使用 my 定义)即可。
  • 没有理由使用& 调用子例程(事实上,它有几个缺点会让您感到困惑)。
  • 除非您知道它们的用途,否则不要使用原型定义子例程。
  • 模块中不需要 shebang 行。
  • 在模块中使用strictwarnings

我会这样写你的代码:

# main.pl
use warnings;
use strict;

use File::Basename; # Not used. Remove?
use Fcntl ':flock'; # Not used. Remove?
use feature qw/say switch/;
use File::Spec::Functions; # Not used. Remove?
use File::Find; # Not used. Remove?
use Sample;

my $LOGFILE = 'sample';
# Lexical filehandle. Three-arg version of open()
open( my $log_fh, '>', $LOGFILE )
  or die "__ERROR: can't open file\n'$LOGFILE'!\n";
flock( $log_fh, LOCK_EX );

print $log_fh ("Tool Start\n");
Sample::func($log_fh);

flock( $log_fh, LOCK_UN );
close( $log_fh );

还有……

package Sample;
use strict;
use warnings;

sub func {
  my ($fh) = @_;
  print $fh ("Printing in subroutine\n");
}

1;

【讨论】:

  • 有什么方法可以避免将文件处理程序传递给子程序?我需要直接访问 main,pl 中的 perl 模块中的文件处理程序。这个要求的原因是因为我可能在每个模块中有不同的 Perl 模块和不同的子例程,每次我需要将文件处理程序传递给 Perl 模块中的每个子例程。另一个困难是不需要从 main.pl 文件中调用子程序,在 *.pm 文件中定义的子程序可能会调用在另一个 *.pm 模块中定义的其他子程序。
  • @RotchMiller:我的回答已经告诉你该怎么做。但我认为这是一个非常糟糕的主意。
  • 主要的潜在问题是如何使文件处理程序在不同 Perl 模块的子例程中可见。就像我们必须使用 EXPORTER 将一个标量变量从一个 *.pm 模块导出到任何 perl 模块一样,文件处理程序的类似概念会很好。
  • @RotchMiller:导出器适用于文件句柄。如果它们是词法变量,显然不是。但是对于包变量和裸字文件句柄,就没有问题了。
  • @RotchMiller 在许多不同模块的子例程中使变量可见的传统方法是将其作为参数传递。但是,如果您想忽略 70 年的良好软件工程实践 - 请随意 :-)
【解决方案2】:

from Dave Cross 的分析非常详细。

在这里,我想提供一种方法来干净地为所有模块提供一个日志文件来写入。

在 sub 中引入一个执行写入日志文件的模块;由所有需要它的模块加载它。在该子中打开要附加的日志文件,使用 state 文件句柄,因此 stays open 在调用中。然后模块通过调用这个 sub 来编写,这可以通过来自main 的调用来启动。

记录器模块

package LogAll;

use warnings;
use strict;
use feature qw(say state);
use Carp qw(croak);    
use Exporter qw(import);

our @EXPORT_OK = qw(write_log);

sub write_log {
    state $fh = do {               # initialize; stays open across calls
        my $log = 'LOG_FILE.txt';
        open my $afh, '>>', $log or croak "Can't open $log: $!";
        $afh;
    };  
    say $fh $_ for @_;
}
1;

另外两个需要记录的模块在这个例子中几乎是一样的;这是一个

package Mod1;

use warnings;
use strict;

use Exporter qw(import);    
use LogAll qw(write_log);

our @EXPORT_OK = qw(f1);

sub f1 {
    write_log(__PACKAGE__ . ": @_");
}
1;

主要

use warnings;
use strict;

use LogAll qw(write_log);    
use Mod1 qw(f1);
use Mod2 qw(f2);

write_log('START');

f1("hi from " . __PACKAGE__);
f2("another " . __PACKAGE__);

文件LOG_FILE.txt中的运行结果

开始 Mod1:来自主要的嗨 Mod2:另一个主

我打印START 进行演示,但文件不必从main 打开。

请酌情进一步开发打印机模块。例如,为文件名添加一种可选的传递方式,以便main 可以命名日志(通过改变参数的类型和数量),并添加一种可控地关闭日志的方式。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-19
    • 1970-01-01
    • 2015-05-15
    • 2012-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多