Reputation: 17724
I wrote this code in perl:
shift( @interfaces = qx'ifconfig -s' );
And got this error:
Type of arg 1 to shift must be array (not list assignment)
When I write it this way:
@interfaces = qx'ifconfig -s';
shift @interfaces;
It does what I want, which is to get the output of the ifconfig command as an array of lines and remove the first element in the array (which is a header, not an actual interface).
My personal preference is to write this as a one liner. It seems to me the parentheses in the first example should cause the assignment to be resolved fully, therefore allowing shift to see @interfaces as an array, but clearly perl thinks it's a "list assignment."
This is surely an easy question for the perl gurus but I've googled and googled and haven't found enlightenment.
If someone would please provide the specific semantics to accomplish what I want in one line I would appreciate it. If you would also please take the time to explain why my first version isn't working I would be eternally grateful (teach a man to fish and all that).
Thank you in advance for your help.
Upvotes: 10
Views: 3898
Reputation: 20280
Starting with Perl version 5.10 you can use the state
variable declaration to manage the persistance of a variable without having to predeclare with my
outside the loop.
use 5.10.0;
my @interfaces = grep {state $i++} qx'ifconfig -s';
yes you can do it without state, but its a perfect use case for it.
Here is the analogous code without state
and same lexicality w.r.t $i
.
my @interfaces;
{
my $i;
@interfaces = grep {$i++} qx'ifconfig -s';
}
or
my @interfaces = do { my $i; grep {$i++} qx'ifconfig -s' };
of course you can not care about $i
's lexicality and simply do
my $i;
my @interfaces = grep {$i++} qx'ifconfig -s';
or you can cheat
my @interfaces = grep {$|++} qx'ifconfig -s'
but that breaks if you rely on $|
somewhere else. Never mind all that, just use state
.
Upvotes: 2
Reputation: 118605
shift @{EXPR}
is valid syntax, so
shift @{$interfaces = [qx'ifconfig -s']}
will give you an array reference that has the first element removed.
I discovered this from the diagnostics
output about calling shift
from list assignment:
$ 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 enforces this behavior on any user-defined subroutine or builtin that is prototyped with \@
or \%
characters. The prototype is a clue for the interpreter that Perl should treat an array or hash function argument as an array or hash type, and not try to unroll the list into several arguments.
One way to think about it (though I'm not sure if this is accurate for the builtins) is that Perl will read the array or hash variable from your list of arguments to a function call, but actually pass a reference to that variable to the prototyped function. So the interpreter needs to identify an array or hash in your argument list, and it needs to be able to get a reference to that array or hash. Perl doesn't or can't (hands waving here) do that with a list assignment expression -- note that the result of
\(@a = (1,2,3))
is a list of 3 references to scalars, not a reference to a list with 3 scalars.
You can see the prototypes (if any) for most of the Perl builtins with the prototype
function:
$ perl -e 'print prototype("CORE::shift")' ===> \@
$ perl -e 'print prototype("CORE::each")' ===> \%
$ perl -e 'print prototype("CORE::push")' ===> \@@
Upvotes: 7
Reputation: 40152
As you saw, shift
requires a literal array, not the result of an assignment. This is because when perl parses shift @interfaces
it is actually rewriting it into something like &CORE::shift(\@interfaces)
and you can not take a reference of an assignment and get an array ref.
You could break it into two lines as you have found, you could hide the assignment inside a bracketed dereference as mob shows, or you could simply throw away the first value:
(undef, @interfaces) = qx'ifconfig -s';
undef
in an lvalue position is a placeholder for values that you don't need.
(parsing of shift
has changed a bit in perl 5.14+, but the argument above still holds)
a few more ways that you probably shouldn't use, ordered only by increasing length :)
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; @_}->();
Upvotes: 13
Reputation: 107040
Try this:
shift qw(this that the other);
You'll get the same error message. The shift
command must take a list variable and not a list itself. After all, there are two major affects with the shift
command.
shift
ing it wouldn't make any sense.In your example, the (@interfaces = qx 'ifconfig -s')
is setting @interfaces
and is returning the value of the list @interfaces
and not the variable itself to the shift
command.
Mob's answer helps you a bit. You'll get a list reference, but then, you'll either have to keep dereferencing it, or set an actual list variable:
shift @{$interfaces = [qx'ifconfig -s']}
foreach my $entry (@{$interfaces}) { #Have to dereference
say "Interface: $entry";
}
@interfaces = @{$interfaces}; #This will also work.
If the whole purpose was to save some typing, setting an actual list variable from the dereferece reference variable doesn't save anything. And, using a reference instead of an actual list in the rest of your program is just going to add a layer of complexity and obfication.
If you are really just setting @interfaces
not to include the first element of the returned list, you can do something like this:
(my $garbage, @interfaces) = qw(ifconfig -s);
The first value of the list will be returned into $garbage
, a throw away variable. The rest of the list will be slurped up by @interfaces
. This is clean, and fairly easy to understand what is going on.
Eric Strom I now see has done it even better:
(undef, @interfaces) = qw(ifconfig -s);
You don't even have a variable to throw out.
Now I'm going to stay up all night worrying what changes Perl 5.14 has made in the parsing of the shift
command.
Upvotes: 1
Reputation: 67918
Perl might not be the easiest way (or most readable way, I should say) to do it, if you want to restrict yourself to using just one line. Here's a solution that fixes your shell command instead:
@interfaces = qx(ifconfig -s | tail -n +2);
Upvotes: 3
Reputation: 1735
The closest I could get to getting this into a one liner:
perl -e '@interfaces = (qx|ifconfig -s|)[1 .. 1000]; print @interfaces'
This is taking a slice from index 1 to index 1000 and assumes your ifconfig output is no more than 1000 lines. Obviously this is horrible programming practice, but is does work for most cases and does what the question asks.
Upvotes: 3