Почему я должен использовать * перед Perl bareword дескриптор файла?

При попытке сделать это:

 my $obj = new JavaScript::Minifier;
 $obj->minify(*STDIN, *STDOUT);
// modified above line to
 $obj->minify(*IP_HANDLE,*OP_HANDLE)

Вышеупомянутые работы, если IP_HANDLE и OP_HANDLE являются дескрипторами файлов, но тем не менее я не в состоянии выяснить что на самом деле * делает при применении к дескриптору файла или любому другому типу данных.

Спасибо,

9
задан brian d foy 11 February 2010 в 10:09
поделиться

3 ответа

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

Техническое название *STDIN, например, typeglob, объясняется в разделе "Typeglobs and Filehandles" раздела perldata. Вы можете встретить манипуляции с typeglobs для различных целей в унаследованном коде. Обратите внимание, что вы можете захватывать typeglobs только глобальных переменных, но никак не лексических.

Передача дескрипторов была распространенной целью для работы непосредственно с typeglobs, но были и другие применения. Подробности см. ниже.

  • Передача дескрипторов файлов в subs
  • Синтаксическая двусмысленность: строка или дескриптор файла
  • Псевдонимы через присвоение typeglob
  • Локализация дескрипторов путем локализации typeglobs
  • Заглянем под капот: *foo{THING} синтаксис
  • Связываем все вместе: DWIM!

Передача ссылок на файлы в subs

В документации perldata объясняется:

Typeglobs и Filehandles

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

[...]

Еще один способ использования typeglobs - передача файловых хэндлов в функцию или создание новых файловых хэндлов. Если вам нужно использовать typeglob для сохранения файла, сделайте это так:

$fh = *STDOUT;

или, возможно, как настоящую ссылку, вот так:

$fh = \*STDOUT;

Примеры использования их в качестве косвенных ссылок на файлы в функциях см. в perlsub.

Ниже приведен ссылочный раздел perlsub.

Передача записей таблицы символов (typeglobs)

ВНИМАНИЕ: Механизм, описанный в этом разделе, изначально был единственным способом имитации передачи по ссылке в старых версиях Perl. Хотя он по-прежнему хорошо работает в современных версиях, новый механизм ссылок в целом проще в работе. См. ниже.

Иногда вы хотите передать в подпрограмму не значение массива, а его имя, чтобы подпрограмма могла изменять глобальную копию массива, а не работать с локальной копией. В Perl вы можете ссылаться на все объекты с определенным именем, добавляя к имени звездочку: *foo. Это часто называют "typeglob", потому что звезду спереди можно рассматривать как подстановочный знак для всех забавных символов префикса переменных, подпрограмм и т.п..

При оценке typeglob выдает скалярное значение, которое представляет все объекты с таким именем, включая любой файл, формат или подпрограмму. Когда оно присваивается, оно заставляет упомянутое имя ссылаться на любое * значение, которое было ему присвоено. [...]

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

Синтаксическая двусмысленность: строка или файловая папка?

Без сигила * голое слово - это просто строка.

Иногда достаточно простых строк. Например, оператор print позволяет

$ perl -le 'print { "STDOUT" } "Hiya!"'
Hiya!

$ perl -le '$h="STDOUT"; print $h "Hiya!"'
Hiya!

$ perl -le 'print "STDOUT" +123'
123

Это не удается при включенном strict 'refs'. Руководство объясняет:

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

В вашем примере рассмотрим синтаксическую двусмысленность. Без знака * вы могли бы иметь в виду строки

$ perl -MO=Deparse,-p prog.pl
use JavaScript::Minifier;
(my $obj = 'JavaScript::Minifier'->new);
$obj->minify('IP_HANDLE', 'OP_HANDLE');

или, может быть, вызов sub

$ perl -MO=Deparse,-p prog.pl
use JavaScript::Minifier;
sub OP_HANDLE {
    1;
}
(my $obj = 'JavaScript::Minifier'->new);
$obj->minify('IP_HANDLE', OP_HANDLE());

или, конечно, файловый хэндл. Обратите внимание в примерах выше, что голое слово JavaScript::Minifier также компилируется как простая строка.

Включите прагму strict, и все равно все пойдет прахом:

$ perl -Mstrict prog.pl
Bareword "IP_HANDLE" not allowed while "strict subs" in use at prog.pl line 6.
Bareword "OP_HANDLE" not allowed while "strict subs" in use at prog.pl line 6.

Псевдонимы через присвоение typeglob

Один трюк с typeglobs, удобный для постов Stack Overflow -

*ARGV = *DATA;

(я мог бы быть более точным с *ARGV = *DATA{IO}, но это немного суетливо. )

Это позволяет оператору diamond <> читать из хэндла файла DATA, как в

#! /usr/bin/perl

*ARGV = *DATA;   # for demo only; remove in production

while (<>) { print }

__DATA__
Hello
there

Таким образом, программа и ее входные данные могут находиться в одном файле, а код более близок к тому, как он будет выглядеть на производстве: просто удалите присвоение typeglob.

Локализация дескрипторов путем локализации typeglobs

Как отмечено в perlsub

Временные значения через local()

ВНИМАНИЕ: В целом, вам следует использовать my вместо local, потому что это быстрее и безопаснее. Исключение составляют глобальные пунктуационные переменные, глобальные файловые ручки и форматы, а также прямые манипуляции с таблицей символов Perl. local в основном используется, когда текущее значение переменной должно быть видно вызываемым подпрограммам. [...]

вы можете использовать typeglobs для локализации ссылок на файлы:

$ cat prog.pl
#! /usr/bin/perl

sub foo {
  local(*STDOUT);
  open STDOUT, ">", "/dev/null" or die "$0: open: $!";
  print "You can't see me!\n";
}

print "Hello\n";
foo;
print "Good bye.\n";

$ ./prog.pl
Hello
Good bye.

"When to Still Use local()" в perlsub имеет другой пример.

2. Вам нужно создать локальный файл или директорию или локальную функцию.

Функция, которой нужен собственный хэндл файла, должна использовать local() на полном typeglob. Это можно использовать для создания новых записей в таблице символов:

sub ioqueue {
 local (*READER, *WRITER); # не мое!
 pipe (READER, WRITER) или die "pipe: $!";
 return (*READER, *WRITER);
}
($head, $tail) = ioqueue();

Чтобы подчеркнуть, этот стиль старомоден. Предпочтительнее избегать глобальных filehandles в новом коде, но возможность понять эту технику в существующем коде полезна.

Заглянем под капот: *foo{THING} синтаксис

Вы можете получить доступ к различным частям typeglob, как объясняет perlref:

Ссылка может быть создана с помощью специального синтаксиса, любовно известного как *foo{THING} синтаксис. *foo{THING} возвращает ссылку на слот THING в *foo (это запись в таблице символов, которая содержит все, что известно как foo).

$scalarref = *foo{SCALAR};
$arrayref = *ARGV{ARRAY};
$hashref = *ENV{HASH};
$coderef = *handler{CODE};
$ioref = *STDIN{IO};
$globref = *foo{GLOB};
$formatref = *foo{FORMAT};

Все эти функции не требуют пояснений, кроме *foo{IO}. Он возвращает дескриптор ввода-вывода, используемый для дескрипторов файлов (open), сокетов (socket и socketpair) и дескрипторов каталогов (opendir). Для совместимости с предыдущими версиями Perl, *foo{FILEHANDLE} является синонимом *foo{IO}, хотя начиная с версии 5.8.0 он устарел. Если действуют предупреждения об устаревании, это предупредит о его использовании.

*foo{THING} возвращает undef, если этот конкретный THING еще не использовался, за исключением скаляров. *foo{SCALAR} возвращает ссылку на анонимный скаляр, если $foo еще не использовался. Это может измениться в будущем выпуске.

*foo{IO} является альтернативой механизму *HANDLE, приведенному в ["Typeglobs and Filehandles" в perldata] для передачи файловых обращений в или из подпрограмм, или хранения в больших структурах данных. Его недостаток в том, что он не создаст для вас новый файл-хэндл. Его преимущество заключается в том, что при присвоении typeglob у вас меньше риска получить больше, чем вы хотите. Однако, если вы присваиваете входящее значение скаляру, а не typeglob, как в примерах ниже, то такой риск отсутствует.

splutter(*STDOUT); # передаем весь glob целиком
splutter(*STDOUT{IO}); # передаем хэндлы файла и dir

sub splutter {
 my $fh = shift;
 print $fh "her um well a hmmm\n";
}

$rec = get_rec(*STDIN); # передаем весь glob целиком
$rec = get_rec(*STDIN{IO}); # передаем хэндлы файлов и директорий

sub get_rec {
 my $fh = shift;
 return scalar <$fh>;
}

Связываем все вместе: DWIM!

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

Поэтому рассмотрим все случаи, которые minify может потребоваться обработать:

  • голое слово
  • голое typeglob
  • ссылка на typeglob
  • filehandle в скаляре

Например:

#! /usr/bin/perl

use warnings;
use strict;

*IP_HANDLE = *DATA;
open OP_HANDLE, ">&STDOUT";
open my $fh, ">&STDOUT";
my $offset = tell DATA;

use JavaScript::Minifier;
my $obj = JavaScript::Minifier->new;
$obj->minify(*IP_HANDLE, "OP_HANDLE");

seek DATA, $offset, 0 or die "$0: seek: $!";
$obj->minify(\*IP_HANDLE, $fh);

__DATA__
Ahoy there
matey!

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

package JavaScript::Minifier;

use warnings;
use strict;

sub new { bless {} => shift }

sub minify {
  my($self,$in,$out) = @_;

  for ($in, $out) {
    no strict 'refs';
    next if ref($_) || ref(\$_) eq "GLOB";

    my $pkg = caller;
    $_ = *{ $pkg . "::" . $_ }{IO};
  }

  while (<$in>) { print $out $_ }
}

1;

Output:

$ ./prog.pl
Name "main::OP_HANDLE" used only once: possible typo at ./prog.pl line 7.
Ahoy there
matey!
Ahoy there
matey!
25
ответ дан 4 December 2019 в 06:49
поделиться

* относится к Perl «typeglob» , который является неясной деталью реализации Perl. Некоторый старый код Perl должен ссылаться на дескрипторы файлов с помощью typeglobs (поскольку в то время не было другого способа сделать это). Более современный код может вместо этого использовать ссылки на дескрипторы файлов, с которыми легче работать.

* аналогичен $ или % , он относится к другому типу объекта, известному под тем же именем.

Со страницы документации perldata :

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

7
ответ дан 4 December 2019 в 06:49
поделиться

Эта команда выполнит то, что вы хотите.

(defun annotate-todo ()
  "put fringe marker on TODO: lines in the curent buffer"
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward "TODO:" nil t)
      (let ((overlay (make-overlay (- (point) 5) (point))))
        (overlay-put overlay 'before-string (propertize "A"
                                                        'display '(left-fringe right-triangle)))))))

При необходимости можно настроить растровое изображение .

Чтобы это можно было применить ко всем файлам, можно добавить его в 'find-file-hooks

(add-hook 'find-file-hooks 'annotate-todo)

Или, если вы хотите, чтобы это было только для определенных режимов, вы можете добавить его к этим режимам.

См. Цветовые поля , Свойство « дисплей» , Наложение и, самое главное, свойство перед строкой .

Примечание. Код был обновлен 27/02/2010 для использования наложений вместо непосредственного добавления свойств текста к текущему тексту.

-121--2212752-

Или маленький язык домена:

a = [1, 2, 3, 4]

FirstMiddleLast.iterate(a) do
  first do |e|
    p [e, 'first']
  end
  middle do |e|
    p [e, 'middle']
  end
  last do |e|
    p [e, 'last']
  end
end

# => [1, "first"]
# => [2, "middle"]
# => [3, "middle"]
# => [4, "last"]

и код, который заставляет его пойти:

class FirstMiddleLast

  def self.iterate(array, &block)
    fml = FirstMiddleLast.new(array)
    fml.instance_eval(&block)
    fml.iterate
  end

  attr_reader :first, :middle, :last

  def initialize(array)
    @array = array
  end

  def first(&block)
    @first = block
  end

  def middle(&block)
    @middle = block
  end

  def last(&block)
    @last = block
  end

  def iterate
    @first.call(@array.first) unless @array.empty?
    if @array.size > 1
      @array[1..-2].each do |e|
        @middle.call(e)
      end
      @last.call(@array.last)
    end
  end

end

Я начал думать: «Если бы только вы могли передать несколько блоков функции Ruby, то вы могли бы иметь ловкое и простое решение этого вопроса». Тогда я понял, что DSL играют маленькие трюки, которые почти как прохождение нескольких блоков.

-121--1000513-

Это глобсигил. * FOO относится к глобусу с именем «FOO», так же как $ FOO относится к скаляру с именем «FOO» и так далее. Как правило, глобусы являются кодовыми ссылками или дескрипторами файлов.

Для изменения значения glob, например * name _ of _ sub = sub {}; , или для получения его значения без вызова специального синтаксиса, например, вызова sub.

2
ответ дан 4 December 2019 в 06:49
поделиться
Другие вопросы по тегам:

Похожие вопросы: