Почему Perl предупреждает, что при открытии моего файла $ fh, $ отсутствуют круглые скобки? - PullRequest
15 голосов
/ 08 апреля 2011

Это мой первый день в 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;

Ответы [ 6 ]

31 голосов
/ 08 апреля 2011

Я взламываю Perl уже более 15 лет, и я признаю, что это предупреждение заставило меня на минуту почесать голову, потому что почти каждый пример вызова open в стандартной документации Perl и почти каждого Perl Учебное пособие существует open без скобок, как вы написали.

Вы написали этот вопрос в первый день работы с Perl, но вы уже включили прагматы strict и warnings! Это отличное начало.

Ложные пуски

Простой, но тупой способ «исправить» предупреждение - отключить все предупреждения. Это был бы ужасный шаг! Предупреждения призваны помочь вам.

Наивные способы подавить предупреждение - отказаться от лексического дескриптора файла в пользу старого плохого способа с голым словом

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 и трактовать $file как буквальное имя файла - это важно, например, при работе с ненадежным пользовательским вводом - использование

open my $fh, "<", $file or die "$0: open $file: $!";

Да, оба закрывают предупреждение, но гораздо более важное преимущество в том, что ваша программа обрабатывает неизбежные ошибки, а не игнорирует их и в любом случае заряжается вперед.

Читайте дальше, чтобы понять, почему вы получили предупреждение, полезные советы о вашей следующей программе Perl, немного философии Perl и рекомендуемые улучшения в вашем коде. Наконец, вы увидите, что ваша программа не требует явного вызова open!

Написать полезные сообщения об ошибках

Обратите внимание на важные компоненты сообщения об ошибке, переданного на die:

  1. программа, которая жаловалась ($0)
  2. что он пытался сделать ("open $file")
  3. почему это не удалось ($!)

Эти специальные переменные описаны в perlvar . Выработайте привычку включать эти важные биты в каждое сообщение об ошибке, которое вы увидите, хотя не обязательно те, которые увидит users . Наличие всей этой важной информации сэкономит время отладки в будущем.

Всегда проверяйте, успешно ли open! 1095 * Еще раз, всегда проверьте, успешно ли open и другие системные вызовы! В противном случае вы получите странные ошибки: $ ./mygrep pattern no-such-file Parentheses missing around "my" list at ./mygrep line 10. readline() on closed filehandle $fh at ./mygrep line 11. Объяснение предупреждений Perl

У предупреждений Perl есть дальнейшее объяснение в документации perldiag , и включение прагмы Diagnostics будет искать объяснения любого предупреждения, которое выдает Perl. С вашим кодом, вывод

$ perl -Mdiagnostics ./mygrep pattern no-such-file
Круглые скобки отсутствуют в "моем" списке в ./mygrep строка 10 (# 1)
(В скобках) Вы сказали что-то вроде

my $foo, $bar = @_;

когда вы имели в виду

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

Помните, что my, our, local и state связываются сильнее, чем запятая.

readline () на закрытой файловой ручке $fh в ./mygrep строка 11 (# 2)
(W закрыто) Файловый дескриптор, с которого вы читаете, закрывался когда-то раньше. Проверьте поток управления.

Параметр командной строки -Mdiagnostics эквивалентен use diagnostics; в вашем коде, но запуск его, как указано выше временно , позволяет выполнять диагностические объяснения без необходимости изменения самого кода.

Предупреждение # 2, потому что no-such-file не существует, но ваш код безоговорочно читает из $fh.

Этоозадачивает, что вы видите предупреждение № 1 на всех!Это первый раз, когда я вспоминаю, что видел это в связи с звонком на open.Документация 5.10.1 содержит 52 примера использования open, включающего лексические дескрипторы файлов, но только у двух из них есть круглые скобки с my.

. Он получает странное и необычное имя:

$ perl -we 'open my $fh, $file'
Name "main::file" used only once: possible typo at -e line 1.
Use of uninitialized value $file in open at -e line 1.

Скобки отсутствуют, так где же предупреждение?!

Однако, добавив одну маленькую точку с запятой, предупреждает об отсутствующих скобках:

$ perl -we 'open my $fh, $file;'
Parentheses missing around "my" list at -e line 1.
Name "main::file" used only once: possible typo at -e line 1.
Use of uninitialized value $file in open at -e line 1.

Давайте посмотрим в исходном коде Perl, чтобы увидеть, откуда исходит предупреждение.

$ grep -rl 'Parentheses missing' .
./t/lib/warnings/op
./op.c
./pod/perl561delta.pod
./pod/perldiag.pod
./pod/perl56delta.pod

Perl_localize в op.c - который обрабатывает my, our, statelocal - содержит следующий фрагмент:

/* 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 Марк Доминус писал: «Конечно, это эвристика, причудливый способ сказать, что она не работает». Эвристика в этом случае тоже не работаети выдает сбивающее с толку предупреждение.

Условное

if (sigil && (*s == ';' || *s == '=')) {

объясняет, почему perl -we 'open my $fh, $file' не предупреждает, а делает с запятой в конце.Посмотрите, что происходит с подобным, но бессмысленным кодом:

$ perl -we 'open my $fh, $file ='
Parentheses missing around "my" list at -e line 1.
syntax error at -e line 1, at EOF
Execution of -e aborted due to compilation errors.

Мы получаем предупреждение!Случай с 3 аргументами open не предупреждает, потому что "<" не позволяет sigil стать истинным, а модификатор or die ... проходит проверку в тупых терминах, потому что токен or начинается с символа, отличного от ; или =.

Целью предупреждения, по-видимому, является полезная подсказка о том, как исправить код, который в противном случае даст неожиданные результаты, например ,

$ perl -lwe 'my $foo, $bar = qw/ baz quux /; print $foo, $bar'
Parentheses missing around "my" list at -e line 1.
Useless use of a constant in void context at -e line 1.
Use of uninitialized value $foo in print at -e line 1.
quux

Здесь предупреждение действительно имеет смысл, но вы обнаружили случай утечки в эвристике.

Чем меньше, тем лучше

В 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
  }
}

Использование нулевого дескриптора файла (также известного как оператор diamond) заставляет ваш код вести себянапример, утилита Unix grep.

  • фильтрует каждую строку каждого файла, указанного в командной строке, или
  • фильтрует каждую строку стандартного ввода, если задан только шаблон

Алмазный оператор также обрабатывает по крайней мере один угловой случай, которого нет в вашем коде.Обратите внимание, что ниже столбец присутствует во входных данных, но не появляется в выходных данных.

$ cat 0
foo
bar
baz
$ ./mygrep bar 0
Parentheses missing around "my" list at ./mygrep line 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: bad pattern (?foo):
Quantifier follows nothing in regex; marked by <-- HERE in
m/? <-- HERE foo/ at ./mygrep line 10.

ThОсновной цикл идиоматичен и компактен.Специальная переменная $_ является аргументом по умолчанию для многих операторов Perl, и разумное использование помогает подчеркнуть что , а не как механизм реализации.

Я надеюсь, что эти предложения помогут!

16 голосов
/ 08 апреля 2011

my - для объявления переменной или их список . В Perl распространена ошибка писать

my $var1, $var2, $var3;

чтобы объявить их всех. Предупреждение должно советовать вам использовать правильную форму:

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

В вашем примере код делает именно то, что вы хотите (вы не получили никаких ошибок или неправильных результатов, не так ли?), Но чтобы было абсолютно ясно, вы можете написать

open my ($fh), $file;

Хотя можно утверждать, что поставить my в середине строки - все равно что скрыть это. Может быть, более читабельным:

my $fh;
open $fh, $file;
2 голосов
/ 08 апреля 2011

Чтобы получить более подробное объяснение предупреждающих сообщений, используйте perldoc диагностика .Например,

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

Если указано более одного значения, список должен быть помещенв скобках.

1 голос
/ 09 апреля 2011

Мне кажется, что ваш код длиннее, чем нужно - вам следует использовать больше лени.

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

Если вы решите, что вам нужны номера строк или имена файлов (возможно, если существует более одного файла), или какая-то другая более сложная печать, то вы, возможно, захотите что-то записать. Но я считаю, что код, который я показываю, эквивалентен тому, который вы показываете.

Обычно я бы добавил use strict; и use warnings; к коду. Однако в этом примере единственная именованная переменная определяется с помощью my (так что строгий не поможет), и здесь не о чем предупреждать. Однако, если вы изучаете Perl или если программа намного сложнее, чем эта, я бы добавил строки use даже после 20 лет использования Perl.

1 голос
/ 08 апреля 2011

Настоящая проблема заключается в том, что пропуск вызовов функций довольно хрупок. Ожидайте странные ошибки, если вы делаете.

$ 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.
0 голосов
/ 17 апреля 2011

Возможно, вы делаете школьный или учебный проект.Но когда я хочу сделать что-то вроде этого, perl, я обычно буду использовать эту более краткую версию вашей программы.

perl -ne 'print if / your_regex /' your_file_list

Для получения дополнительной информациипопробуйте

perldoc perlrun

и найдите объяснения -n и -p.

...