为什么我必须在Perl裸字文件句柄前使用*?

sud*_*03r 9 perl operators

在尝试这样做时:

 my $obj = new JavaScript::Minifier;
 $obj->minify(*STDIN, *STDOUT);
// modified above line to
 $obj->minify(*IP_HANDLE,*OP_HANDLE)
Run Code Online (Sandbox Code Playgroud)

如果IP_HANDLE和OP_HANDLE是文件句柄,上面的工作原理,但我仍然无法弄清楚*当应用于文件句柄或任何其他数据类型时实际上做了什么.

谢谢,

Gre*_*con 25

在perl v5.6之前的旧时代,它引入了词法文件句柄 - 十多年前 - 现在传递文件和目录句柄很尴尬.您问题中的代码是使用这种老式风格编写的.

*STDIN例如,技术名称是一个typeglob,在perldata"Typeglobs和Filehandles"部分中进行了解释.在遗留代码中,您可能会出于各种目的使用typeglobs.请注意,您可以仅获取全局变量的typeglobs,而不是lexicals.

传递句柄是直接处理typeglobs的常见目的,但也有其他用途.请参阅下文了解详情.

  • 将文件句柄传递给subs
  • 句法歧义:字符串或文件句柄
  • 别名通过typeglob赋值
  • 通过本地化typeglobs来本地化句柄
  • 窥视引擎盖:*foo{THING}语法
  • 将它们捆绑在一起:DWIM!

将文件句柄传递给subs

perldata文档解释:

Typeglobs和Filehandles

Perl使用称为typeglob的内部类型来保存整个符号表条目.typeglob的类型前缀是a,*因为它代表所有类型.这曾经是通过引用将数组和哈希值传递给函数的首选方法,但是现在我们有了真正的引用,这很少需要.

[...]

typeglobs的另一个用途是将文件句柄传递给函数或创建新的文件句柄.如果您需要使用typeglob来保存文件句柄,请按以下方式执行:

$fh = *STDOUT;
Run Code Online (Sandbox Code Playgroud)

或者作为真正的参考,像这样:

$fh = \*STDOUT;
Run Code Online (Sandbox Code Playgroud)

有关在函数中使用这些作为间接文件句柄的示例,请参阅perlsub.

perlsub引用部分如下.

传递符号表条目(typeglobs)

警告:本节中描述的机制最初是模拟旧版Perl中的pass-by-reference的唯一方法.虽然它在现代版本中仍然可以正常工作,但新的参考机制通常更容易使用.见下文.

有时您不希望将数组的值传递给子例程,而是传递给它的名称,以便子例程可以修改它的全局副本而不是使用本地副本.在Perl中,您可以通过在名称前加上星号来引用特定名称的所有对象:*foo.这通常被称为"typeglob",因为前面的星可以被认为是变量和子例程等所有有趣的前缀字符的通配符匹配.

在计算时,typeglob会生成一个标量值,表示该名称的所有对象,包括任何文件句柄,格式或子例程.分配给时,它会使所提到的名称引用*分配给它的任何值.[...]

请注意,typeglob只能用于全局变量,而不能用于词法.注意上面的警告.更喜欢避免这种模糊的技术.

句法歧义:字符串还是文件句柄?

如果没有这个*印记,裸字只是一个字符串.

简单的字符串有时就足够了.例如,print运营商允许

$ perl -le 'print { "STDOUT" } "Hiya!"'
Hiya!

$ perl -le '$h="STDOUT"; print $h "Hiya!"'
Hiya!

$ perl -le 'print "STDOUT" +123'
123
Run Code Online (Sandbox Code Playgroud)

这些失败并strict 'refs'启用.手册解释说:

FILEHANDLE可以是标量变量名,在这种情况下,变量包含文件句柄的名称或对文件句柄的引用,从而引入一个级别的间接.

在您的示例中,请考虑语法歧义.没有*印记,你可能意味着字符串

$ perl -MO=Deparse,-p prog.pl
use JavaScript::Minifier;
(my $obj = 'JavaScript::Minifier'->new);
$obj->minify('IP_HANDLE', 'OP_HANDLE');
Run Code Online (Sandbox Code Playgroud)

或者可能是一个子呼叫

$ perl -MO=Deparse,-p prog.pl
use JavaScript::Minifier;
sub OP_HANDLE {
    1;
}
(my $obj = 'JavaScript::Minifier'->new);
$obj->minify('IP_HANDLE', OP_HANDLE());
Run Code Online (Sandbox Code Playgroud)

或者,当然,文件句柄.请注意,在上面的示例中,裸字JavaScript::Minifier也可以编译为简单的字符串.

启用strictpragma,无论如何它都会出现在窗口中:

$ perl -Mstrict prog.pl
Bareword "IP_HANDLE" not allowed while "strict subs" in use at prog.pl line 6.
Bareword "OP_HANDLE" not allowed while "strict subs" in use at prog.pl line 6.

别名通过typeglob赋值

对于Stack Overflow帖子来说,使用typeglobs的一个技巧很方便

*ARGV = *DATA;
Run Code Online (Sandbox Code Playgroud)

(我可以更准确地说*ARGV = *DATA{IO},但这有点挑剔.)

这允许钻石操作员<>DATA文件句柄中读取,如下所示

#! /usr/bin/perl

*ARGV = *DATA;   # for demo only; remove in production

while (<>) { print }

__DATA__
Hello
there
Run Code Online (Sandbox Code Playgroud)

这样,程序及其输入可以在一个文件中,并且代码与它在生产中的外观更接近:只需删除typeglob赋值.

通过本地化typeglobs来本地化句柄

perlsub中所述

临时值通过 local()

警告:一般情况下,您应该使用my而不是local,因为它更快更安全.例外情况包括全局标点变量,全局文件句柄和格式,以及Perl符号表本身的直接操作.local当变量的当前值必须对被调用的子例程可见时,主要使用.[...]

你可以使用typeglobs本地化文件句柄:

$ cat prog.pl
#! /usr/bin/perl

sub foo {
  local(*STDOUT);
  open STDOUT, ">", "/dev/null" or die "$0: open: $!";
  print "You can't see me!\n";
}

print "Hello\n";
foo;
print "Good bye.\n";

$ ./prog.pl
Hello
Good bye.
Run Code Online (Sandbox Code Playgroud)

local()在perlsub中"何时仍然使用"还有另一个例子.

2.您需要创建本地文件或目录句柄或本地函数.

需要自己的文件句柄的函数必须local()在完整的typeglob上使用.这可用于创建新的符号表条目:

sub ioqueue {
    local (*READER, *WRITER); # not my!
    pipe (READER, WRITER) or die "pipe: $!";
    return (*READER, *WRITER);
}
($head, $tail) = ioqueue();
Run Code Online (Sandbox Code Playgroud)

要强调的是,这种风格是老式的.更喜欢在新代码中避免使用全局文件句柄,但能够理解现有代码中的技术是有用的.

窥视引擎盖:*foo{THING}语法

perlref解释说,你可以得到一个typeglob的不同部分:

可以使用特殊语法(称为语法)创建引用*foo{THING}.*foo{THING}返回对THING插槽的引用*foo(这是符号表条目,其中包含所有称为foo的内容).

$scalarref = *foo{SCALAR};
$arrayref = *ARGV{ARRAY};
$hashref = *ENV{HASH};
$coderef = *handler{CODE};
$ioref = *STDIN{IO};
$globref = *foo{GLOB};
$formatref = *foo{FORMAT};
Run Code Online (Sandbox Code Playgroud)

所有这些都是不言自明的,除了*foo{IO}.它返回IO句柄,用于文件句柄(open),套接字(socketsocketpair)以及目录句柄(opendir).为了与以前版本的Perl兼容,它*foo{FILEHANDLE}是一个同义词*foo{IO},但从5.8.0开始不推荐使用.如果弃用警告生效,则会警告其使用.

*foo{THING}undef如果尚未使用特定的THING,则返回,除了标量的情况.*foo{SCALAR}如果$foo尚未使用,则返回对匿名标量的引用.这可能会在将来的版本中发生变化.

*foo{IO}*HANDLE[perldata中的"Typeglobs和Filehandles"]中给出的机制的替代方法,用于将文件句柄传入或传出子例程,或存储到更大的数据结构中.它的缺点是它不会为你创建一个新的文件句柄.它的优点是,使用typeglob赋值的风险比你想要的更少.(但它仍然会混淆文件和目录句柄.)但是,如果您将传入值分配给标量而不是像下面的示例中那样使用typeglob,则不存在发生这种情况的风险.

splutter(*STDOUT); # pass the whole glob
splutter(*STDOUT{IO}); # pass both file and dir handles

sub splutter {
  my $fh = shift;
  print $fh "her um well a hmmm\n";
}

$rec = get_rec(*STDIN); # pass the whole glob
$rec = get_rec(*STDIN{IO}); # pass both file and dir handles

sub get_rec {
  my $fh = shift;
  return scalar <$fh>;
}
Run Code Online (Sandbox Code Playgroud)

将它们捆绑在一起:DWIM!

上下文是Perl的关键.在您的示例中,虽然语法可能不明确,但意图不是:即使参数是字符串,这些字符串显然也用于命名文件句柄.

因此,请考虑所有minify可能需要处理的案例:

  • 裸字
  • 裸的小球
  • 对typeglob的引用
  • 标量中的文件句柄

例如:

#! /usr/bin/perl

use warnings;
use strict;

*IP_HANDLE = *DATA;
open OP_HANDLE, ">&STDOUT";
open my $fh, ">&STDOUT";
my $offset = tell DATA;

use JavaScript::Minifier;
my $obj = JavaScript::Minifier->new;
$obj->minify(*IP_HANDLE, "OP_HANDLE");

seek DATA, $offset, 0 or die "$0: seek: $!";
$obj->minify(\*IP_HANDLE, $fh);

__DATA__
Ahoy there
matey!
Run Code Online (Sandbox Code Playgroud)

作为图书馆作者,住宿可能很有用.为了说明,JavaScript :: Minifier的以下存根理解传递文件句柄的传统方式和现代方式.

package JavaScript::Minifier;

use warnings;
use strict;

sub new { bless {} => shift }

sub minify {
  my($self,$in,$out) = @_;

  for ($in, $out) {
    no strict 'refs';
    next if ref($_) || ref(\$_) eq "GLOB";

    my $pkg = caller;
    $_ = *{ $pkg . "::" . $_ }{IO};
  }

  while (<$in>) { print $out $_ }
}

1;
Run Code Online (Sandbox Code Playgroud)

输出:

$ ./prog.pl
Name "main::OP_HANDLE" used only once: possible typo at ./prog.pl line 7.
Ahoy there
matey!
Ahoy there
matey!

  • 很好的彻底回答! (3认同)
  • 很棒 - 我希望我可以加倍投票.值得注意的是,目前关于最佳实践的思考是使用词法句柄(`打开我的$ foo,'<',$ filename;`),这避免了对这些东西的需要.它超出了问题的严格范围,但是typeglob赋值也是如何将子例程安装到包中的.`\*foo = sub {print"Sub叫"};`.你已经为typeglobs制作了一个很棒的资源.谢谢. (3认同)

Gre*_*ill 7

所述*Perl的"类型团",这是Perl的一个不起眼的实现细节.一些较旧的Perl代码需要使用typeglobs引用文件句柄(因为当时没有任何其他方法可以执行此操作).更现代的代码可以使用文件句柄引用,这更容易使用.

*到类似于$%,它指的是不同类型的对象通过相同的名称已知的.

perldata文档页面:

Perl使用称为typeglob的内部类型来保存整个符号表条目.typeglob的类型前缀是*,因为它代表所有类型.这曾经是通过引用将数组和哈希值传递给函数的首选方法,但是现在我们有了真正的引用,这很少需要.