Преобразование результата контекста списка в массив в одну строку в perl? - PullRequest
10 голосов
/ 26 октября 2011

Я написал этот код в Perl:

shift( @interfaces = qx'ifconfig -s' );

И получил эту ошибку:

Type of arg 1 to shift must be array (not list assignment)

Когда я пишу это так:

@interfaces = qx'ifconfig -s';
shift @interfaces;

Этоделает то, что я хочу, чтобы получить вывод команды ifconfig в виде массива строк и удалить первый элемент в массиве (который является заголовком, а не фактическим интерфейсом).

Мои личные предпочтениянаписать это как один лайнер.Мне кажется, что круглые скобки в первом примере должны привести к полному разрешению назначения, поэтому Shift может видеть @interfaces как массив, но ясно, что perl считает это «списочным назначением».

Это, безусловно,простой вопрос для Perl-гуру, но я гуглил и гуглял и не нашел просветления.

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

Заранее спасибоза вашу помощь.

Ответы [ 6 ]

13 голосов
/ 26 октября 2011

Как вы видели, shift требует буквенного массива, а не результата присваивания.Это потому, что когда Perl анализирует shift @interfaces, он фактически переписывает его в нечто вроде &CORE::shift(\@interfaces), и вы не можете взять ссылку на присвоение и получить массив ref.

Вы можете разбить его на две строки каквы обнаружили, что можете скрыть назначение внутри разыменования в квадратных скобках, как показывает mob , или вы можете просто выбросить первое значение:

(undef, @interfaces) = qx'ifconfig -s';

undef в положении lvalueзаполнитель для значений, которые вам не нужны.

(синтаксический анализ shift немного изменился в perl 5.14+, но приведенный выше аргумент все еще остается в силе)


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

my @interfaces = sub {shift; @_}->(qx'ifconfig -s');

my @interfaces = sub {@_[1..$#_]}->(qx'ifconfig -s');

my @interfaces = map {@$_[1..$#$_]} [qx'ifconfig -s'];

my @interfaces = map {shift @$_; @$_} [qx'ifconfig -s'];

our @interfaces; shift @{*interfaces = [qx'ifconfig -s']};

my @interfaces = sub {*_ = [qx'ifconfig -s']; shift; @_}->();
7 голосов
/ 26 октября 2011

shift @{EXPR} - допустимый синтаксис, поэтому

shift @{$interfaces = [qx'ifconfig -s']}

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

Я обнаружил это из вывода diagnostics о вызове shift из списка назначения:

$ perl -Mdiagnostics -e 'print shift(@a = (2,3,4))'
Type of arg 1 to shift must be array (not list assignment) at -e line 1, at end of line
 Execution of -e aborted due to compilation errors (#1)
    (F) This function requires the argument in that position to be of a
    certain type.  Arrays must be @NAME <b>or @{EXPR}</b>.  Hashes must be
    %NAME or %{EXPR}.  No implicit dereferencing is allowed--use the
    {EXPR} forms as an explicit dereference.  See perlref.

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

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

\(@a = (1,2,3))

- это список из 3 ссылок на скаляры, а не список из трех ссылок на скаляры.

Вы можете увидеть прототипы (если таковые имеются) для большинства встроенных в Perl функций prototype:

$ perl -e 'print prototype("CORE::shift")'      ===>   \@
$ perl -e 'print prototype("CORE::each")'       ===>   \%
$ perl -e 'print prototype("CORE::push")'       ===>   \@@
3 голосов
/ 26 октября 2011

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

@interfaces = qx(ifconfig -s | tail -n +2);
3 голосов
/ 26 октября 2011

Самое близкое, что я мог получить к получению этого в один слой:

perl -e '@interfaces = (qx|ifconfig -s|)[1 .. 1000]; print @interfaces'

Это берет фрагмент от индекса 1 до индекса 1000 и предполагает, что ваш вывод ifconfig не превышает 1000 строк. Очевидно, это ужасная практика программирования , но она работает в большинстве случаев и выполняет то, что задает вопрос.

2 голосов
/ 26 октября 2011

Начиная с Perl версии 5.10, вы можете использовать объявление переменной state для управления постоянством переменной без необходимости предварительно объявить my вне цикла.

use 5.10.0; 
my @interfaces = grep {state $i++} qx'ifconfig -s'; 

да, вы можете это сделатьбез состояния, но это идеальный вариант использования для него.Вот аналогичный код без state и той же лексичности относительно $i.

my @interfaces;
{
  my $i;
  @interfaces = grep {$i++} qx'ifconfig -s';
}

или

my @interfaces = do { my $i; grep {$i++} qx'ifconfig -s' };

Конечно, вы не можете заботиться о лексичности $i ипросто сделайте

my $i;
my @interfaces = grep {$i++} qx'ifconfig -s';

или вы можете обмануть

my @interfaces = grep {$|++} qx'ifconfig -s'

, но это сломается, если вы положитесь на $| где-то еще.Не берите в голову все это, просто используйте state.

1 голос
/ 26 октября 2011

Попробуйте это:

shift qw(this that the other);

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

  1. Возвращает значение первого элемента списка
  2. Также удаляет значение первого элемента из переменной списка. Если переменная списка отсутствует, shift не будет иметь никакого смысла.

В вашем примере (@interfaces = qx 'ifconfig -s') устанавливает @interfaces и возвращает значение списка @interfaces, а не саму переменную в команду shift.

Mob's ответ вам немного поможет. Вы получите ссылку на список, но затем вам придется либо разыменовать ее, либо установить фактическую переменную списка:

shift @{$interfaces = [qx'ifconfig -s']}
foreach my $entry (@{$interfaces}) {   #Have to dereference
   say "Interface: $entry";
}
@interfaces = @{$interfaces};          #This will also work.

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

Если вы на самом деле просто устанавливаете @interfaces, чтобы не включать первый элемент возвращаемого списка, вы можете сделать что-то вроде этого:

(my $garbage, @interfaces) = qw(ifconfig -s);

Первое значение списка будет возвращено в $garbage, переменную выброса. Остальная часть списка будет подмешана на @interfaces. Это чисто и довольно легко понять, что происходит.

Эрик Стром Теперь я понял, что сделал это еще лучше:

(undef, @interfaces) = qw(ifconfig -s);

Вы даже не можете выбросить переменную.

Теперь я буду спать всю ночь, беспокоясь о том, какие изменения внес Perl 5.14 в парсинг команды shift.

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