我已经 hack Perl 超过 15 年了,我承认这个警告让我一时摸不着头脑,因为几乎每个open
标准 Perl 文档中的示例调用和几乎每个现有Perl 教程open
都包含没有括号,只是就像你写的一样。
您在使用 Perl 的第一天就写了这个问题,但您已经启用了strict
和warnings
pragmata!这是一个很好的开始。
错误的开始
“修复”警告的一种简单但愚蠢的方法是禁用所有警告。这将是一个可怕的举动!警告旨在帮助您。
压制警告的幼稚方法是放弃词法文件句柄,转而使用裸词的坏旧方法
open FH, $file;
使用显式括号open
open(my $fh, $file);
使my
' 括号显式
open my($fh), $file;
使用带括号的括号
(open my $fh, $file);
或使用 3-argument open
。
open my $fh, "<", $file;
我建议不要单独使用其中任何一个,因为它们都有一个严重的共同点。
最好的方法
通常,消除有关缺少括号的警告的最佳方法是不添加括号!
始终检查是否open
成功,例如,
open my $fh, $file or die "$0: open $file: $!";
要禁用 Perl 的魔法打开并将$file
其视为文件的文字名称(例如,在处理不受信任的用户输入时很重要),请使用
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 文档中有进一步的解释,启用诊断杂注将查找 perl 发出的任何警告的解释。使用您的代码,输出为
$ perl -Mdiagnostics ./mygrep pattern no-such-file
./mygrep
第 10 行 (#1)的“我的”列表周围缺少
括号(W 括号)你说过类似的话
my $foo, $bar = @_;
当你的意思是
my ($foo, $bar) = @_;
请记住my
,our
,local
和state
绑定比逗号更紧密。
readline() on closed filehandle $fh
at ./mygrep
line 11 (#2)
(W closed) 您正在读取的文件句柄在现在之前的某个时间自行关闭。检查您的控制流。
-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
在 op.c — 处理my
、our
、state
和—local
包含以下代码段:
/* 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");
}
注意第一行的注释。在我的垃圾邮件生活中,Mark Dominus 写道:“当然,这是一种启发式方法,这是一种说它不起作用的奇特方式。” 在这种情况下,启发式也不起作用并产生令人困惑的警告。
有条件的
if (sigil && (*s == ';' || *s == '=')) {
解释了为什么perl -we 'open my $fh, $file'
不警告但使用尾随分号。观察类似但无意义的代码会发生什么:
$ perl -we '打开我的 $fh, $file ='
-e 第 1 行的“我的”列表周围缺少括号。
-e 第 1 行,EOF 处的语法错误
由于编译错误,-e 的执行中止。
我们收到警告!3-argument open
case 不会发出警告,因为它"<"
阻止sigil
了变为 true,并且or die ...
修饰符通过了集合,用钝的术语来说,因为标记以oror
以外的字符开头。;
=
警告的意图似乎是为如何修复会产生令人惊讶的结果的代码提供有用的提示,例如,
$ perl -lwe '我的 $foo, $bar = qw/ baz quux /; 打印 $foo, $bar'
-e 第 1 行的“我的”列表周围缺少括号。
在 -e 第 1 行的 void 上下文中无用地使用常量。
在 -e 第 1 行的 print 中使用未初始化的值 $foo。
曲子
在这里,警告确实有意义,但您发现的情况是启发式泄漏。
少即是多
Perl 的语法糖使得编写Unix 风格的过滤器变得容易,正如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
尊重用户的路径。
与其盲目假设用户在命令行上至少提供了一个模式,不如检查它是否存在或提供有用的使用指南。
因为您的模式存在于变量中,所以它可能会改变。这很难说是深奥,但这意味着每次您的代码计算时可能需要重新编译模式/$pattern/
,即,对于每一行输入。使用qr//
可以避免这种浪费,还可以检查用户在命令行上提供的模式是否是有效的正则表达式。
$ ./mygrep ?foo
./mygrep: 错误模式 (?foo):
量词不遵循正则表达式;由 <-- 这里标记
米/?<-- 这里 foo/ 在 ./mygrep 第 10 行。
主循环既惯用又紧凑。特殊变量是许多 Perl 运算符的$_
默认参数,明智地使用有助于强调实现机制的内容而不是方式。
我希望这些建议对您有所帮助!