Perl error, cant use defined(#array). How can i fix this? - perl

I am getting this error -> "Can't use 'defined(#array)' (Maybe you should just omit the defined()?)"
On this line of code:
if ( defined( #{ $linkbot{"$nroboton"} } ) ) {
How can i fix this?

defined tests whether a scalar value is undef, therefore it's nonsensical on an array. You can test whether the scalar is defined before using it as an arrayref, or if you're trying to test if the array is empty, just removed the defined() as the error message says.
# if this hash value is defined
if (defined $linkbot{$nroboton}) {
# if this referenced array has elements
if (#{ $linkbot{$nroboton} }) {

Use define on the variable $nroboton itself, and/or if (#{$linkbot{$nroboton}}) for the anonymous array whose reference is the value for that key, as explained.
Once you need to check on any of that it stands to reason that you may well also need to test whether there is a key $nroboton in the hash %linkbot, with exists
if ( exists $linkbot{$nroboton} ) { ... } # warning if $nroboton undef
so altogether
if (defined $nroboton and exists $linkbot{$nroboton}) { ... }
and now you can check and work with the data in the arrayref, #{$linkbot{$nroboton}}.
Note that there is no need to double-quote that variable; it will be evaluated.

Related

Perl syntax (replacing 'defined(#array')

Let me start by saying I haven't programmed in Perl in a LONG time.
Currently trying to get some older code to work that relies on defined with an array.
Code (abridged):
# defined outside the file-reading block
my %refPRE;
# line from a file
#c = split();
if (defined #{$refPRE{$c[0]}})
{
# do stuff
}
Now this won't run like this because of the following error:
Can't use 'defined(#array)' (Maybe you should just omit the defined()?)
Fine, but if I removed the defined then I get the following error:
Can't use an undefined value as an ARRAY reference.
I can see what it's trying to do (if $c[0] is in the $refPRE then do this, else do something else) but I'm not familiar enough with Perl to figure out what the right way to get this to work is. Hoping this is trivial for someone.
Apparently posting here is all the catalyst I needed...
Switching if (defined #{$refPRE{$c[0]}}) to if ($refPRE{$c[0]}) was sufficient to work! Hopefully this helps someone else who searching for this (specific) problem...
Can't use an undefined value as an ARRAY reference.
This is saying that $refPRE{ $c[0] } is returning undef, and you cannot dereference undef as an array.
#{ undef } # will error
You don't need to deref this at all. If it returns undef, it's false. If it returns anything else, that will (likely) be true.
if ( $refPRE{$c[0]} )
{
my $foo = #{ $refPRE{$c[0]} };
# do stuff
}
Looking at your second error $refPRE{$c[0]} can be undefined so #{ ... } is failing. You can fix this using the undef or opperator // like this.
if (#{ $refPRE{$c[0]} // [] }) { ... }
This checks if $refPRE{$c[0]} is defined and if not returns an empty anonymous array. An empty array is false in an if statement.

Error: Type of argument to keys on reference must be unblessed [duplicate]

This question already has answers here:
Type of argument to keys on reference must be unblessed hashref or arrayref
(2 answers)
Closed 4 years ago.
I am getting the error
Type of of argument to keys on reference must be unblessed hasref or arrayref at xxxx.pl line 6518
Part of the script is
ndr_log("Processing file: $file [$filesize bytes] Portion: $portion; Billing Date/MDate: $portion_date/$filemtime; Reading Proc: [$entries/$entries_reading/$entries_n15]: $reading_proc", 'Info');
if (!defined $BILL->{$portion}->{$portion_date}->{mtime}
or $BILL->{$portion}->{$portion_date}->{mtime} < $filemtime) {
$BILL->{$portion}->{$portion_date}->{mtime} = $filemtime;
$BILL->{$portion}->{$portion_date}->{entries} = $entries;
$BILL->{$portion}->{$portion_date}->{readings} = $entries_reading;
$BILL->{$portion}->{$portion_date}->{n15} = $entries_n15;
$BILL->{$portion}->{$portion_date}->{proc} = $reading_proc;
$BILL->{$portion}->{$portion_date}->{ok} = $meter_ok;
$BILL->{$portion}->{$portion_date}->{nok} = $meter_nok;
}
else {
DbgPlain("Ignoring file: $file; this is older result; never version exists: $BILL->{$portion}->{$portion_date}->{mtime}");
}
} # for files
# Create Report
#
my #REPORT;
for (sort keys $BILL) { ### Line 6518
my $portion = $_;
for ( sort keys $BILL->{$portion} ) {
my $portion_date = $_;
#ndr_log("Portion: $portion on $portion_date")
my $entries = $BILL->{$portion}->{$portion_date}->{entries};
my $entries_reading = $BILL->{$portion}->{$portion_date}->{readings};
my $entries_n15 = $BILL->{$portion}->{$portion_date}->{n15};
my $reading_proc = $BILL->{$portion}->{$portion_date}->{proc};
my $meter_ok = $BILL->{$portion}->{$portion_date}->{ok};
my $meter_nok = $BILL->{$portion}->{$portion_date}->{nok};
Is there anyway to solve it?
I have in my new server ActivePerl 5.16.3 build 1604 and in the one that works Build 1603. Is it a version issue?
If $BILL is a blessed object, getting its keys makes no sense. It might have a method to provide a list of the "keys" that you can then use as method names or parameters to a universal getter - but we don't know, as you haven't shown how $BILL is populated and what class it's been blessed to.
There were some changes in the behaviour of keys and references. To stay on the safe side, don't use it at all, dereference the reference:
keys %$BILL
But that applies to plain non-blessed hash references only. Applying dereference on an object breaks encapsulation (and note that $BILL->{$portion} dereferences the object, too).
**Line 1618:** for (sort keys $BILL) {
$BILL is a scalar value (starts with $), specifically a reference to a hash as can be seen how it was used earlier. You need to call keys on the hash itself and not on the reference to it. To do this you need to dereference it to get to the hash and call keys on it, i.e. use keys %$BILL not keys $BILL.

Perl get parameter datatype

Am trying to make a subroutine that replaces data depending on datatype: the problem is i can't get the datatype of the parameter, i used this:
sub replace {
my ($search, $replacement, $subject) = #_;
if (ref($search) eq "HASH") {
print "r is a reference to a HASH.\n";
}
elsif (ref($search) eq "SCALAR") {
print "r is a reference to a SCALAR.\n";
}
elsif (ref($search) eq "ARRAY") {
print "r is a reference to a ARRAY.\n";
}
}
my $str = "Foo";
my #arr = ("Foo");
replace($str);
replace(#arr);
But none works. am really new to perl
ref() takes a reference to something, not the something itself. Here:
replace($str);
replace(#arr);
...you are sending in the something directly. Send in a reference to the something instead, by putting a \ in front of it (which says, "take a reference to this something"):
replace(\$str);
replace(\#arr);
Output:
r is a reference to a SCALAR.
r is a reference to a ARRAY.
Note also that in your replace() function, in this line:
my ($search, $replacement, $subject) = #_;
You are effectively asking for a scalar value as the thing to search, so passing in a list (array, hash etc) will clobber $replacement and $subject if the passed in list has more than one element, so you may want to do something like this to ensure you're getting the proper params, and nothing is clobbered unexpectedly:
sub replace {
my ($search, $replacement, $subject) = #_;
die "first arg must be a ref\n" if ! ref $search;
Of course, you can do further argument checking, but this'll ensure that the first parameter can only be a reference to something. Instead of die(), you can also just return so the program doesn't crash, or print or warn and then return.
It is not stated what you want to do with it, but here's what is wrong with what you show.
The ref function shows the datatype of the reference subtmitted to it, or it returns an empty string if its argument isn't a reference at all.
So to get the expected behavior you should do
replace(\$str);
replace(\#arr);
Also, you need to add the test to your function
else (not ref $search)
for when a submitted string is not a reference.
For completeness, I should also point out an issue, explained in the answer by stevieb. When you pass an array to a function, it receives it as a flat list of arguments. With your function you clearly do not want replace(#arr). They are assigned to your list of scalar variables in order, one element to each. (As soon as there is an array variable it all goes into it.) See, for example, this post.

Why does this statment return true

Why does this statement,
if (! $ssh_options{user}) {
delete $ssh_options{user};
}
return true, but this statement
if ($ssh_options{user} eq 'undef') {
delete $ssh_options{user};
}
give me the error,
Use of uninitialized value $ssh_options{"user"} in string eq at analyze.pl line 230.
I thought you always had to have something for perl to compare the value of the variable being compared.
** Update **
# Quick Joe Smith
I cannot base my comparison on whether the hash keys exist or not because the values for this hash
sub ssh_connect {
my $host = shift;
my %ssh_options = (
port => shift,
user => shift,
password => shift
);
Come from this function which calls the sub routine
if ((exists $config_file{user}) && (exists $config_file{password})) {
my $vmware_user = $config_file{user};
my $vmware_password = $config_file{password};
ssh_connect($vmware_host, $vmware_port, $vmware_user, $vmware_password);
} else {
ssh_connect($vmware_host, $vmware_port);
}
Perl hash slots have a number of ways they can be false. If the key has never been filled, the exists function will return false. If the key exists but contains an undefined value, exists will be true but the defined function will return false. In normal boolean context, as given by an if statement, a hash key that was never filled, or is undefined, '', 0 or anything that reduces to those will be false.
From your update, it sounds like what you want is to use defined
delete $ssh_options{user} unless defined $ssh_options{user};
Use exists for checking hash keys.
if (exists $ssh_options{user}) {
...
}
However, in your case, it seems as though you're checking for existing keys whose values may be undefined, in which case:
if (defined $ssh_options{user}) {
...
}
The above may be what you want.
As a sidenote, checking to see if the value of any variable is defined in the way you described:
if ($something eq 'undef') {
...
}
Is wrong. You're checking to see if the variable contains the string "undef". What you need in those situations is:
unless (defined $something) {
...
}
But if $ssh_options{"user"} is uninitialized then it it doesn't have a value to compare to.
With Perl, undefined evaluates to false in Boolean context. So your first test 'works' as you expect. However, it might introduce a bug if zero or an empty string is a legitimate value for $ssh_options{user} to hold, if you don't wish to treat that the same as undef. That's because your first test doesn't differentiate between false as a value, and false as undefined. As far as Perl's Boolean evaluation goes, all of the following are "false": 0, '' (empty string), undef, or an empty list.
Your second snippet of code fails because undef should be a bareword, not a quoted string. 'undef' as a quoted string IS a value, which would actually evaluate to 'true' in Boolean tests. Because you're comparing an actual value to your hash element, Perl warns you that you're comparing something to an undefined value. In this case, the undefined value is $ssh_options{user}. It's actually good that Perl is warning you; it's given you a clue as to what you're doing wrong.
If you really want to test whether $ssh_options{user} is defined, use the defined() function. If you want to test whether $ssh_options{user} exists, use the exists() function.

Is there a simple way to validate a hash of hash element exists and is defined?

I need to validate a Perl hash of hash element such as $Table{$key1}{$key2} to exist and be defined. Here is what I do. (I have no idea $key1 even exists)
if
((defined $Table{$key1}) &&
(exists $Table{$key1}) &&
(defined $Table{$key1}{$key2}) &&
(exists $Table{$key1}{$key2}))
{
#do whatever
}
Is there an easier and cleaner way to do it?
You don't need to check each level of the heirarchy: you can just go for the value you care about. exists doesn't check for definedness, only if the slot in the hash exists (it could exist with an undefined value), so if you care that the value is defined, you would need to call defined rather than exists. If a value is not defined, it evaluates in boolean context to false, so we can type a little less and reduce your example to:
if ($Table{$key1}{$key2})
{
# do whatever
}
However, if the value in that key is defined but is "false" (numerically evaluates to zero, or is the empty string), this can cause a false negative, so we should explicitly check for definedness if this is a possibility:
if (defined $Table{$key1}{$key2})
{
# do whatever
}
If you don't want to autovivify $Table{$key1}, you can check for its existence first, which brings us to the "best" way for the general case:
if (exists $Table{$key1} and defined $Table{$key1}{$key2})
{
# do whatever
}
If you're going to do this a lot for various fields in a hash, you may want to add some OO-style accessor methods which would do this work for you:
sub has_field
{
my ($this, $fieldName) = #_;
return exists $this->{data} && defined $this->{data}{$fieldName});
}
I'm sure you've read it already, but it can't hurt to read the relevant documentation again:
perldoc -f exists
perldoc perldata
perldoc perldsc
Given an expression that specifies a hash element or array element, exists returns true if the specified element in the hash or array has ever been initialized, even if the corresponding value is undefined. The element is not autovivified if it doesn't exist.
...
A hash or array element can be true only if it's defined, and defined if it exists, but the reverse doesn't necessarily hold true.
The following is shorter and will protect from autovivifcation:
if (exists $table{$key1} and defined $table{$key1}{$key2}) {...}
The other checks in your code are not needed.
Check existence first, then defined-ness. (A value can exist without being defined but not be defined without existing.) You should test the intermediate levels with exists to prevent unintended autovivification. For the last level you only need to call defined. When there aren't too many layers it's easy to code directly:
if (exists $hash{a} && defined $hash{a}{b}) {...}
This gets awkward if there are many layers:
if (exists $hash{a} && exists $hash{a}{b} && exists $hash{a}{b}{c} ...) {...}
In that case, you can write a version of defined that doesn't autovivify intermediate values:
sub safe_defined {
my $h = shift;
foreach my $k (#_) {
if (ref $h eq ref {}) {
return unless exists $h->{$k};
$h = $h->{$k};
}
else {
return;
}
}
return defined $h;
}
You use it this way:
if (safe_defined(\%hash, qw(a b c))) {
say $hash{a}{b}{c};
}
Note: This version of the function is limited.
It only handles nested hashes. Perl lets you construct arbitrary data
structures, like a hash of arrays of scalar references...
It doesn't support blessed references (i.e. objects).
A truly generic version is left as an exercise for the reader. ;)
You could check out Data::Diver. It dives into data structures without autovivifying. The syntax would be:
if ( defined Dive(\%Table, $key1, $key2) ) { ... }
or even:
if ( defined(my $value = Dive(\%Table, $key1, $key2) ) ) {
...do something with $value...
}
Great! Thanks you all for the reply.
Since the autovivifying is an issue for me, currently i am using the "awkward" approach, i.e.
if (exists $Table{$key1} && defined $Table{$key1}{$key2}) {
Do whatever
}
It works for me, however as you guys said, i have 3-4 level deep of nested hash, the code is bit of messy.
I will check out Data:Diver. That one looks nicer.
Thanks, again,