Преобразование результата контекста списка в массив в одной строке в perl?

Я написал этот код в 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 в виде массива строк и удалить первый элемент в массиве (который является заголовком, а не фактическим интерфейсом).

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

Это, несомненно, простой вопрос для perl гуру, но я googled и googled и не нашел просветления.

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

Заранее благодарим вас за помощь.

Ответ 1

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

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

(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; @_}->();

Ответ 2

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 or @{EXPR}.  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 ссылок на скаляры, а не ссылку на список с 3-мя скалярами.

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

$ perl -e 'print prototype("CORE::shift")'      ===>   \@
$ perl -e 'print prototype("CORE::each")'       ===>   \%
$ perl -e 'print prototype("CORE::push")'       ===>   \@@

Ответ 3

Ближайший я мог бы получить это в один лайнер:

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

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

Ответ 4

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

@interfaces = qx(ifconfig -s | tail -n +2);

Ответ 5

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

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

да, вы можете сделать это без состояния, но это идеальный вариант для него. Вот аналогичный код без state и той же лексикальности w.r.t $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.

Ответ 6

Попробуйте следующее:

shift qw(this that the other);

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

  • Он возвращает значение первого элемента списка
  • Он также удаляет значение первого элемента из переменной списка. Если нет переменной списка, shift ing это не имеет никакого смысла.

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

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

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

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

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

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

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

Эрик Стром Теперь я вижу, что это еще лучше:

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

У вас даже нет переменной для выброса.

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