Преобразование результата контекста списка в массив в одной строке в 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
.