Почему Perl предупреждает, что при открытии моего $fh, $file отсутствуют скобки?

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

person zjk    schedule 08.04.2011    source источник
comment
Всегда, всегда, всегда проверяйте, удается ли open!   -  person Greg Bacon    schedule 08.04.2011
comment
используйте три аргумента open   -  person Nikhil Jain    schedule 08.04.2011


Ответы (6)


Я взламываю 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. Выработайте привычку включать эти важные фрагменты в каждое сообщение об ошибке, которое вы увидите, хотя и не обязательно в те, которые увидят пользователи. Наличие всей этой важной информации сэкономит время на отладку в будущем.

Всегда проверяйте, удалось ли open!

Еще раз, всегда проверяйте успешность 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, а включение прагма диагностики будет искать объяснения любого предупреждения, которое выдает Perl. С вашим кодом вывод

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

my $foo, $bar = @_;

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

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

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

readline() для закрытого дескриптора файла $fh at ./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, 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");
}

Обратите внимание на комментарий к первой строке. В статье Моя жизнь со спамом Марк Доминус пишет: «Конечно, это это эвристика, то есть причудливый способ сказать, что это не работает». Эвристика в этом случае тоже не работает и выдает запутанное предупреждение.

условное

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.

Получаем предупреждение! Случай 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
  }
}

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

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

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

$ 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.

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

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

person Community    schedule 08.04.2011
comment
Грег, я всегда использую круглые скобки в своих открытых примерах в своих руководствах. Я делаю это, потому что мне слишком сложно запомнить приоритет. Я всегда путаюсь с or и // и прочим, поэтому я просто использую союзы C, которые я действительно понимаю. И поэтому я всегда использую скобки. Читать тоже легче. - person tchrist; 08.04.2011
comment
Можешь объяснить причину непереключения - в @ARGV в пояснении ‹›? Я новичок в Perl. Спасибо! - person Alby; 19.04.2012
comment
@Alby Обратите внимание, что это условно и происходит только тогда, когда ‹code›@ARGV‹/code› пусто. Волшебное слово Perl open трактует — как синоним стандартного ввода, из которого считывается пустой дескриптор файла или оператор алмаза, когда в командной строке нет аргументов. - person Greg Bacon; 19.04.2012
comment
Спасибо. Это объяснение проясняет мое первоначальное замешательство: зачем вставлять то, что вы уберете в следующей строке? :) - person Alby; 19.04.2012

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

my $var1, $var2, $var3;

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

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

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

open my ($fh), $file;

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

my $fh;
open $fh, $file;
person Daniel Böhmer    schedule 08.04.2011
comment
+1 за то, что поместил меня в отдельную строку ... так легче заметить. - person Alex Feinman; 08.04.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

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

person toolic    schedule 08.04.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.
person ikegami    schedule 08.04.2011
comment
Это. Я настоятельно рекомендую использовать круглые скобки для всех вызовов функций. - person tchrist; 08.04.2011

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

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

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

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

person Jonathan Leffler    schedule 09.04.2011

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

perl -ne 'печатать, если /your_regex/' your_file_list

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

perldoc perlrun

и поищите объяснения -n и -p.

person perlhelper    schedule 17.04.2011