我已经 hack Perl 超过 15 年了,我承认这个警告让我一时摸不着头脑,因为在标准 Perl 文档中几乎每个示例都调用 open 并且几乎每个 Perl现有教程包含open,没有括号,就像你写的一样。
您在使用 Perl 的第一天就写了这个问题,但您已经启用了 strict 和 warnings 实用程序!这是一个很好的开始。
假开始
“修复”警告的一种简单但愚蠢的方法是禁用所有警告。这将是一个可怕的举动!警告旨在帮助您。
消除警告的幼稚方法是放弃lexical filehandle,转而使用裸词的旧方法
open FH, $file;
在open 中使用显式括号
open(my $fh, $file);
明确my 的括号
open my($fh), $file;
使用带括号的括号
(open my $fh, $file);
或使用 3 参数 open。
open my $fh, "<", $file;
我建议反对单独使用其中任何一种,因为它们都有一个严重的共同点。
最好的方法
一般而言,消除有关缺少括号的警告的最佳方法是添加无括号!
始终检查open 是否成功,例如,
open my $fh, $file or die "$0: open $file: $!";
要禁用 Perl 的 magic open 并将 $file 视为文件的字面名称——这很重要,例如,在处理 untrusted user input 时——使用
open my $fh, "<", $file or die "$0: open $file: $!";
是的,两者都会关闭警告,但更重要的好处是您的程序可以处理不可避免的错误,而不是忽略它们并继续收费。
继续阅读以了解您收到警告的原因、关于您的下一个 Perl 程序的有用提示、一些 Perl 理念以及对代码的改进建议。最后,您会看到您的程序不需要显式调用open!
编写有用的错误消息
注意传递给die的错误消息的重要组成部分:
- 投诉的程序 (
$0)
- 它试图做什么 (
"open $file")
- 为什么会失败 (
$!)
这些特殊变量记录在perlvar 中。现在养成将这些重要信息包含在您将看到的每条错误消息中的习惯——尽管不一定是用户会看到的那些。掌握所有这些重要信息将节省未来的调试时间。
始终检查open 是否成功!
再一次,总是检查open和其他系统调用是否成功!否则,您最终会遇到奇怪的错误:
$ ./mygrep 模式 no-such-file
./mygrep 第 10 行的“我的”列表周围缺少括号。
在 ./mygrep 第 11 行关闭文件句柄 $fh 上的 readline()。
Perl 警告说明
Perl 的警告在perldiag documentation 中有进一步的解释,启用diagnostics pragma 将查找perl 发出的任何警告的解释。使用您的代码,输出是
$ perl -Mdiagnostics ./mygrep pattern no-such-file
./mygrep 第 10 行 (#1) 的“我的”列表周围缺少括号
(W括号)你说过类似
my $foo, $bar = @_;
你的意思
my ($foo, $bar) = @_;
请记住,my、our、local 和 state 的绑定比逗号更紧密。
在 ./mygrep 第 11 行关闭文件句柄 $fh 上的 readline() (#2)
(W 关闭)您正在读取的文件句柄在现在之前的某个时间自行关闭。检查您的控制流。
-Mdiagnostics 命令行选项等效于您代码中的use diagnostics;,但按上述方式运行它暂时可以启用诊断说明,而无需修改您的代码本身。
警告 #2 是因为 no-such-file 不存在,但您的代码无条件从 $fh 读取。
令人费解的是,您完全看到了警告 #1!这是我记得第一次看到它与对open 的调用有关。 5.10.1 文档有 52 个使用 open 的示例,涉及词法文件句柄,但其中只有 两个 带有带有 my 的括号。
它变得越来越好奇:
$ perl -we '打开我的 $fh, $file'
名称“main::file”仅使用一次:-e 第 1 行可能有错字。
在 -e 第 1 行打开时使用未初始化的值 $file。
括号不见了,那么警告在哪里?!
然而,添加一个小分号,确实警告缺少括号:
$ perl -we '打开我的 $fh, $file;'
-e 第 1 行的“我的”列表周围缺少括号。
名称“main::file”仅使用一次:-e 第 1 行可能有错字。
在 -e 第 1 行打开时使用未初始化的值 $file。
让我们看看 perl 的源代码,看看警告来自哪里。
$ grep -rl '括号丢失' 。
./t/lib/警告/操作
./op.c
./pod/perl561delta.pod
./pod/perldiag.pod
./pod/perl56delta.pod
Perl_localize in op.c——处理my、our、state和local——包含以下sn-p:
/* some heuristics to detect a potential error */
while (*s && (strchr(", \t\n", *s)))
s++;
while (1) {
if (*s && strchr("@$%*", *s) && *++s
&& (isALNUM(*s) || UTF8_IS_CONTINUED(*s))) {
s++;
sigil = TRUE;
while (*s && (isALNUM(*s) || UTF8_IS_CONTINUED(*s)))
s++;
while (*s && (strchr(", \t\n", *s)))
s++;
}
else
break;
}
if (sigil && (*s == ';' || *s == '=')) {
Perl_warner(aTHX_ packWARN(WARN_PARENTHESIS),
"Parentheses missing around \"%s\" list",
lex
? (PL_parser->in_my == KEY_our
? "our"
: PL_parser->in_my == KEY_state
? "state"
: "my")
: "local");
}
注意第一行的注释。在My Life With Spam 中,Mark Dominus 写道:“当然,这是一种启发式方法,这是一种说它不起作用的奇特方式。”在这种情况下,启发式也不起作用,并产生令人困惑的警告。
条件
if (sigil && (*s == ';' || *s == '=')) {
解释了为什么perl -we 'open my $fh, $file' 不发出警告,而是使用尾随分号发出警告。观察类似但无意义的代码会发生什么:
$ perl -we '打开我的 $fh, $file ='
-e 第 1 行的“我的”列表周围缺少括号。
-e 第 1 行,EOF 处的语法错误
-e 的执行由于编译错误而中止。
我们收到警告! 3 参数open 的情况不会发出警告,因为"<" 阻止sigil 变为真,并且or die ... 修饰符通过了集合,用钝的术语来说,因为or 标记以@ 以外的字符开头987654387@或=。
警告的意图似乎是为如何修复会产生令人惊讶的结果的代码提供有用的提示,例如,
$ perl -lwe '我的 $foo, $bar = qw/ baz quux /;打印 $foo, $bar'
-e 第 1 行的“我的”列表周围缺少括号。
在 -e 第 1 行的 void 上下文中无用地使用常量。
在 -e 第 1 行的 print 中使用未初始化的值 $foo。
qux
这里,警告确实有意义,但您发现的情况是启发式泄漏。
少即是多
Perl 的语法糖使编写Unix-style filters 变得容易,如perlop 文档中所述。
空文件句柄<> 很特别:它可以用来模拟sed 和awk 的行为。 <> 的输入要么来自标准输入,要么来自命令行中列出的每个文件。下面是它的工作原理:第一次评估<>,检查@ARGV 数组,如果它为空,则$ARGV[0] 设置为"-",打开时会为您提供标准输入。然后将@ARGV 数组作为文件名列表进行处理。循环
while (<>) {
... # code for each line
}
等价于以下类似 Perl 的伪代码:
unshift(@ARGV, '-') unless @ARGV;
while ($ARGV = shift) {
open(ARGV, $ARGV);
while (<ARGV>) {
... # code for each line
}
}
使用空文件句柄(也称为菱形运算符)使您的代码表现得像 Unix grep 实用程序。
- 过滤命令行命名的每个文件的每一行,或者
- 仅在给定模式时过滤标准输入的每一行
菱形运算符还可以处理您的代码无法处理的至少一种极端情况。请注意下面的条形图出现在输入中,但未出现在输出中。
$ 猫 0
富
酒吧
巴兹
$ ./mygrep 栏 0
./mygrep 第 10 行的“我的”列表周围缺少括号。
继续阅读,了解菱形运算符如何提高可读性、表达经济性和正确性!
建议的代码改进
#! /usr/bin/env perl
use strict;
use warnings;
die "Usage: $0 pattern [file ..]\n" unless @ARGV >= 1;
my $pattern = shift;
my $compiled = eval { qr/$pattern/ };
die "$0: bad pattern ($pattern):\n$@" unless $compiled;
while (<>) {
print if /$compiled/;
}
不要硬编码到perl 的路径,而是使用env 来尊重用户的PATH。
与其盲目假设用户在命令行上至少提供了一个模式,不如检查它是否存在或提供有用的使用指南。
因为您的模式存在于变量中,所以它可能会改变。这几乎没有什么意义,但这意味着每次您的代码为每一行输入计算/$pattern/,即时,可能需要重新编译该模式。使用qr// 可以避免这种浪费,还可以检查用户在命令行中提供的模式是否是有效的正则表达式。
$ ./mygrep ?foo
./mygrep: 错误模式 (?foo):
量词不遵循正则表达式;由
主循环既惯用又紧凑。 $_ 特殊变量是许多 Perl 运算符的默认参数,明智地使用有助于强调 what 而不是实现机制的方式。
希望这些建议对您有所帮助!