Определение функции изменяет внешнее поведение <> - PullRequest
1 голос
/ 16 июня 2019

ВАЖНО: Мотивация для этого вопроса - не решить проблему, а понять поведение Perl.


Рассмотрим следующий игрушечный скрипт:

#!/usr/bin/env perl

use strict;

sub main {

  @ARGV >= 2 or die "$0: not enough arguments\n";

  my $arg_a = shift @ARGV;
  my $arg_b = shift @ARGV;

  while ( <> ) {
    print "+++ $_";
  }
}

main();

__END__

Этот скрипт принимает 2 или более аргументов (которые он не использует).Все, что он делает, это выводит (с префиксом +++) либо его стандартный ввод, либо содержимое, сколько файлов указано в качестве его третьего, четвертого и т. Д. Аргументов.

Пока код ведет себя какЯ ожидаю этого.

Теперь рассмотрим эту слегка измененную версию:

#!/usr/bin/env perl

use strict;

sub slurp {
  local $/ = undef;
  local @ARGV = @_;
  return <>;
}

sub main {

  @ARGV >= 2 or die "$0: not enough arguments\n";

  my $arg_a = shift @ARGV;
  my $arg_b = shift @ARGV;

  my $content = slurp( $arg_a );

  while ( <> ) {
    print "+++ $_";
  }
}

main();

__END__

Эта версия скрипта не игнорирует свой первый аргумент;скорее он интерпретирует как путь к файлу и считывает его содержимое в переменную $content (которую он впоследствии игнорирует).Кроме этого, скрипт должен вести себя точно так же, как и раньше.

К сожалению, эта версия скрипта больше не отображает его стандартный ввод (хотя он по-прежнему отображает содержимое своих 3-го, 4-го и т. Д. Аргументов).

Я знаю, что проблема связана со способом реализации функции slurp, потому что, если я изменю эту реализацию на

sub slurp {
  local $/ = undef;
  open my $input, $_[ 0 ] or die "$!";
  return <$input>;
}

..., тогда сценарий еще раз повторяет своеstdin, когда доступно.

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

Ответы [ 2 ]

2 голосов
/ 16 июня 2019

Вам нужно исчерпать итератор (вызывая его до тех пор, пока он не вернет undef), прежде чем он снова подумает об использовании STDIN.

sub slurp {
   local $/ = undef;
   local @ARGV = @_;
   my $rv = <>;   # Read file specified by $_[0].
   1 while <>;    # Exhaust the iterator.
   return $rv;
}

или

sub slurp {
   local $/ = undef;
   local @ARGV = @_;
   my $rv = "";
   while (my $file = <>) {
      $rv .= $file;
   }

   return $rv;  # Concatenation of all files specified by @_.
}
2 голосов
/ 16 июня 2019

Чтобы <> работал с STDIN, он должен вызываться, когда @ARGV пусто. Если в @ARGV есть имена файлов при запуске <>, они удаляются оттуда по мере чтения файлов, а затем вам нужно снова вызвать <> , чтобы дождаться STDIN.

perl -wE'if (@ARGV) { print while <> }; print while <>' file

Второй print while <> ожидает STDIN (без него печатается file и программа завершается).

В принципе, это может произойти с вашим сабвуфером, если бы он прочитал все файлы с @ARGV и как только элемент управления вернулся к вызову <> в главном, который затем ожидал бы STDIN.

Однако ваш подчиненный локализует @ARGV (хорошая практика!), Поэтому после выхода из глобального @ARGV все равно имеет то, что делал в начале. & dagger; Затем while в основном считывает эти файлы (снова), получает тот undef, который он должен получить в конце последнего файла, и завершается.

Один из способов увидеть это: удалить все из @ARGV после вызова подпрограммы, считывающей input, и перед while в main. Затем этот while будет ждать STDIN снова, независимо от подпрограммы. Как

perl -wE'
    sub ri { local @ARGV = @_; return <> }; 
    print for ri(@ARGV); 
    say"argv: @ARGV";
    @ARGV=(); 
    print while <>
' file

(Обратите внимание на то, что ваш пример использует два файла, в то время как подпрограмма имеет дело с одним, так что даже если подпрограмма должна была использовать глобальный @ARGV (не local -ized) и удалить файл из @ARGV, там все равно остался бы один файл, чтобы занимать while в главном. Так что вы все равно не получите STDIN.)

Еще один способ увидеть все это: добавить еще один print while <>, в конце; что один будет ждать STDIN.

Все это описано в Операторы ввода / вывода (perlop) , хотя это требует довольно близкого прочтения.


& dagger; & thinsp; При local $GLOBAL_VAR; значение $GLOBAL_VAR копируется и восстанавливается при выходе из этой области. Таким образом, local защищает глобальную переменную от изменений в пределах своей области.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...