15

这是我使用 perl 的第一天,我发现这个警告非常令人困惑。

./grep.pl 第 10 行的“我的”列表周围缺少括号。

它似乎

open FILE, $file;

工作正常。

出什么问题了

open my $fh, $file;

谢谢!

#!/usr/bin/perl

use strict;
use warnings;

sub grep_all {
        my $pattern = shift;

        while (my $file = shift) {
                open my $fh, $file;
                while (my $line = <$fh>) {
                        if ($line =~ m/$pattern/) {
                                print $line;
                        }   
                }   
        }   
}

grep_all @ARGV;
4

6 回答 6

31

我已经 hack Perl 超过 15 年了,我承认这个警告让我一时摸不着头脑,因为几乎每个open标准 Perl 文档中的示例调用和几乎每个现有Perl 教程open都包含没有括号,只是就像你写的一样。

您在使用 Perl 的第一天就写了这个问题,但您已经启用了strictwarningspragmata!这是一个很好的开始。

错误的开始

“修复”警告的一种简单但愚蠢的方法是禁用所有警告。这将是一个可怕的举动!警告旨在帮助您。

压制警告的幼稚方法是放弃词法文件句柄,转而使用裸词的坏旧方法

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

  1. 投诉的程序 ( $0)
  2. 它试图做什么 ( "open $file")
  3. 为什么失败($!

这些特殊变量记录在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) = @_;

请记住myour,localstate绑定比逗号更紧密。

readline() on closed filehandle $fhat ./mygrepline 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 — 处理myourstate和—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 opencase 不会发出警告,因为它"<"阻止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 运算符的$_默认参数,明智地使用有助于强调实现机制的内容而不是方式。

我希望这些建议对您有所帮助!

于 2011-04-08T15:40:27.637 回答
16

my用于声明一个变量或它们的列表。Perl 中的一个常见错误是这样写

my $var1, $var2, $var3;

宣布所有这些。警告应该建议您使用正确的形式:

my ($var1, $var2, $var3);

在您的示例中,代码完全符合您的要求(您没有得到任何错误或错误的结果,是吗?),但要使其绝对清楚,您可以编写

open my ($fh), $file;

尽管有人可能会争辩说,把它放在my中间就像隐藏它。也许更具可读性:

my $fh;
open $fh, $file;
于 2011-04-08T14:57:54.400 回答
2

要获得对警告消息的更详细解释,请使用perldoc diagnostics。例如,

use strict;
use warnings;
use diagnostics;

my $fh, $file;

将生成以下有用的解释:

“我的”列表周围缺少括号(W 括号)你说过类似的话

    my $foo, $bar = @_;

when you meant

    my ($foo, $bar) = @_;

Remember that "my", "our", and "local" bind tighter than comma.

您还可以在命令提示符下查看my的文档:

perldoc -f my

如果列出了多个值,则必须将列表放在括号中。

于 2011-04-08T15:26:25.580 回答
1

真正的问题是忽略函数调用是相当脆弱的。如果你这样做,预计会出现奇怪的错误。

$ perl -we'$file="abc"; open(my $fh, $file);'

$ perl -we'$file="abc"; open my $fh, $file;'
Parentheses missing around "my" list at -e line 1.
于 2011-04-08T18:34:28.660 回答
1

在我看来,你的代码比它需要的要长——你应该使用更多的懒惰。

#!/usr/bin/env perl
my $pattern = shift;
while (<>)
{
    print if m/$pattern/;
}

如果您决定需要行号或文件名(可能有多个文件)或其他一些更复杂的打印,那么您可能需要将内容写出来。但我相信我展示的代码和你展示的代码是等价的。

通常,我会在代码中添加use strict;和。use warnings;然而,在这个例子中,唯一的命名变量是用定义的my(所以严格不会有帮助),并且没有什么需要警告的。但是,如果您正在学习 Perl,或者如果程序比这复杂得多,我会添加这些use行,即使在使用 Perl 大约 20 年后也是如此。

于 2011-04-09T13:35:08.660 回答
0

你可能正在做一个学校或学习项目。但是当我想做类似 perl 的事情时,我通常会使用你的程序的这个更简洁的版本。

perl -ne 'print if /your_regex/' your_file_list

欲了解更多信息,请尝试

perldoc perlrun

并寻找 -n 和 -p 的解释。

于 2011-04-17T19:32:20.577 回答