Почему perl предупреждает, что в моем файле $fh, $отсутствуют скобки?

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

Ответы

Ответ 1

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

open my $fh, "<", $file;

Я рекомендую не использовать какие-либо из них сами, потому что все они имеют серьезное упущение.

Лучший подход

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

Всегда проверяйте, успешно ли open, например,

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

Чтобы отключить Perl magic open и обработать $file как литеральное имя файла, важное, например, при работе с ненадежный пользовательский ввод -use

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

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

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

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

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

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

Эти специальные переменные документируются в 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 на ./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");
}

Обратите внимание на комментарий в первой строке. В 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
  }
}

Использование нулевой дескриптор файла (также известный как оператор алмаза) заставляет ваш код вести себя как утилита 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):\[email protected]" 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, и разумное использование помогает подчеркнуть то, что, а не как механизм внедрения.

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

Ответ 2

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

my $var1, $var2, $var3;

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

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

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

open my ($fh), $file;

Хотя можно утверждать, что размещение my в середине строки похоже на скрытие. Возможно, более читаемый:

my $fh;
open $fh, $file;

Ответ 3

Чтобы получить более подробное объяснение предупреждающих сообщений, используйте 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

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

Ответ 4

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

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

Ответ 5

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

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

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

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

Ответ 6

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

perl -ne 'print if/your_regex/' your_file_list

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

perldoc perlrun

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