Use of uninitialized value in concatenation (.) or string in slapd_ munin plugin - perl

I'm trying to implement the slapd_ munin plugin which is written in perl which I'm pretty much clueless about. The full plugin is available here. The error I'm getting is this one:
Use of uninitialized value in concatenation (.) or string at
/etc/munin/plugins/slapd_localhost line 232, <DATA> line 275.
Line 232 is this one:
my $searchdn = $ops{$action}->{'search'} . "," . $basedn;
I tried debugging by outputing all the variables/objects as follows:
use Data::Dumper; # top of script
# [...]
print Dumper(%ops);
print "action = [$action]\n";
print "basedn = [$basedn]\n\n";
my $searchdn = $ops{$action}->{'search'} . "," . $basedn;
When I run it again here is what I obtain:
[...] # 15 other variables belonging to $ops
$VAR16 = {
'info' => 'The graph shows the number of Waiters',
'search' => 'cn=Waiters',
'desc' => 'The current number of Waiters',
'filter' => '(|(cn=Write)(cn=Read))',
'title' => 'Number of Waiters',
'label2' => {
'read' => 'Read',
'write' => 'Write'
},
'vlabel' => 'Waiters'
};
action = [localhost]
action = [cn=Monitor]
Use of uninitialized value in concatenation (.) or string at /etc/munin/plugins/slapd_localhost line 237, <DATA> line 275.
Since all the variables seem to be set, I really don't understand the error message I'm getting
Q: Can anybody advise on how debugging this script?

You should dump a reference to %ops, as in
print Dumper \%ops;
This will make the debug output clearer. To illustrate, consider the output of
#! /usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my %h = (foo => { bar => 1 }, baz => { quux => 3 });
print "No reference:\n",
Dumper(%h),
"\n",
"Reference:\n",
Dumper(\%h);
Notice how you see the structure much more clearly in the latter half:
No reference:
$VAR1 = 'baz';
$VAR2 = {
'quux' => 3
};
$VAR3 = 'foo';
$VAR4 = {
'bar' => 1
};
Reference:
$VAR1 = {
'baz' => {
'quux' => 3
},
'foo' => {
'bar' => 1
}
};
You cut out a critical bit of the output. What's the value of $VAR15? Is it "localhost" or something else?
When you print $searchdn, what is its value?

Related

How to print an element from an array which is inside the hash in perl

I'm trying to print the outputs from an API which are in multidimensional format.
use strict;
use warnings;
use Data::Dumper;
my $content={
'school_set' => 'SSET1234',
'result' => [
{
'school_name' => 'school_abc',
'display_value' => 'IL25',
'school_link' => 'example.com',
'status' => 'registerd',
'status_message' => 'only arts',
'school_id' => '58c388d40596191f',
}
],
'school_table' => 'arts_schools'
};
print "school_name is=".$content{result}[0]{school_name};
print "school_status is=".$content{result}[3]{status};
output
Global symbol "%content" requires explicit package name (did you forget to declare "my %content"?) at test8.pl line 20.
Global symbol "%content" requires explicit package name (did you forget to declare "my %content"?) at test8.pl line 21.
I have to print the outputs like below from the result.
school_name = school_abc
school_status = registered
If $content is a hash reference, you need to dereference it first. Use the arrow operator for that:
$content->{result}[0]{school_name}
The syntax without the arrow is only possible for %content.
my %content = ( result => [ { school_name => 'abc' } ] );
print $content{result}[0]{school_name};
If you want to print all the results, you have to loop over the array somehow. For example
#!/usr/bin/perl
use warnings;
use strict;
my $content = {
'result' => [
{
'school_name' => 'school_abc',
'status' => 'registerd',
},
{
'school_name' => 'school_def',
'status' => 'pending',
}
],
};
for my $school (#{ $content->{result} }) {
print "school_name is $school->{school_name}, status is $school->{status}\n";
}
Your data structure assumes an array, perhaps it would be useful to utilize loop output for the data of interest.
The data presented as hash reference and will require de-referencing to loop through an array.
Following code snippet is based on your posted code and demonstrates how desired output can be achieved.
use strict;
use warnings;
use feature 'say';
my $dataset = {
'school_set' => 'SSET1234',
'result' => [
{
'school_name' => 'school_abc',
'display_value' => 'IL25',
'school_link' => 'example.com',
'status' => 'registerd',
'status_message' => 'only arts',
'school_id' => '58c388d40596191f',
}
],
'school_table' => 'arts_schools'
};
for my $item ( #{$dataset->{result}} ) {
say "school_name is = $item->{school_name}\n"
. "school_status is = $item->{status}";
}
exit 0;
Output
school_name is = school_abc
school_status is = registerd

trying to return a hash from a perl function and getting "can't use string ("0") as a HASH ref...."

I have a Perl function (named readDicomFile) which ends thusly:
return { 'fileProperties' => \%fileProperties, 'filehandle' => *FH, 'buffersize' => $buffersize };
the code that calls it looks like this:
$file = readDicomFile( $ARGV[0] );
#use Data::Dumper;
#print Dumper $file;
my #imageData;
local *FH = $file->{filehandle};
while ( read(FH, $_, $file->{buffersize}) ) {
push #imageData, unpack( 'S' x ( $file->{buffersize}/($file->{fileProperties}->{bitsAllocated}/8) ), $_ );
}
print "DEBUG: found ", (scalar #imageData), " elements\n";
I get this output:
Can't use string ("0") as a HASH ref while "strict refs" in use at ./test.pl line 17.
DEBUG: found 262156 elements
When I try to figure out what's happening with my data structure, I uncomment the two lines for using Data::Dumper and I get this:
$VAR1 = {
'fileProperties' => {
'echoNumber' => '',
'highBit' => 11,
'rows' => 512,
'bitsAllocated' => 16,
'modality' => 'CT',
'echoTime' => '',
'windowCenter' => '200',
'studyDescription' => 'CT SINUS / FACIAL WITH CONTRAST ',
'repetitionTime' => '',
'sequenceName' => '',
'method' => 'perl method',
'seriesNumber' => '502 ',
'imageNumber' => '0 ',
'windowWidth' => '50',
'trailer' => 0,
'pixelRepresentation' => 0,
'sliceLocation' => '',
'bitsStored' => 12,
'ultrasoundColorData' => '',
'rescaleIntercept' => 0,
'photometricInterpretation' => 'MONOCHROME2 ',
'description' => 'Patient Protocol',
'imageDataType' => '',
'imagePosition' => '',
'columns' => 512,
'studyDate' => '20140505'
},
'filehandle' => *Radiology::Images::FH,
'buffersize' => '1024'
};
I've played around with several different idioms for returning the hash values from the function (like passing back a hash or a hashref), but I'm always getting the same error.
Does anyone have insight into my problem?
Thanks in advance.
EDIT:
I've been playing around with this all afternoon. Here is the entirety of the current test.pl
#!/usr/bin/perl -w
use lib '/etc/perl';
use strict;
use Radiology::Images;
my $file = $ARGV[0];
$file = readDicomFile( $file );
print STDERR "DEBUG: $file->{fileProperties}->{bitsAllocated}\n";
my #imageData;
# while ( $readsize = read ( $file->{filehandle}, $_, $file->{buffersize} ) ) {
# push #imageData, unpack( 'S' x ( $file->{buffersize}/($file->{fileProperties}->{bitsAllocated}/8) ), $_ );
# }
my $readsize;
my $imagesize = $file->{fileProperties}->{columns} * $file->{fileProperties}->{rows};
print "DEBUG: should find $imagesize elements\n";
while ( $imagesize > 0 ) {
$readsize = read ( $file->{filehandle}, $_, $file->{buffersize} );
push #imageData, unpack( 'S' x ( $readsize/( $file->{fileProperties}->{bitsAllocated}/8 ) ), $_ );
$imagesize -= $readsize /( $file->{fileProperties}->{bitsAllocated}/8 );
}
print "DEBUG: found ", (scalar #imageData), " elements\n";
...which gives me this output
DEBUG: 16
DEBUG: should find 262144 elements
Can't use string ("0") as a HASH ref while "strict refs" in use at /root/test.pl line 7.
DEBUG: found 262144 elements
...HOWEVER, when I change the 'while' loop in line #15 to
while ( $imagesize > 34816 ) {
I get this output:
DEBUG: 16
DEBUG: should find 262144 elements
DEBUG: found 227328 elements
So, it would appear that something I'm doing in a loop between lines 15 and 18 causes an error that goes back in time to line 7. Thus, my problem has never been with passing the hash back from my function. ??
BTW, the number 34816 was arrived experimentally. 34816 doesn't trigger the error, 34815 does.
Given this is looking completely whacky and given that the code works the way I think it is supposed to despite the error, I guess I'll just assume it is a language bug and turn my attention to just suppressing the error message.
2nd EDIT:
This is test.pl now:
#!/usr/bin/perl -w
use lib '/etc/perl';
use strict;
use Radiology::Images;
my $file = $ARGV[0];
$file = readDicomFile( $file );
my #imageData;
my $readsize;
while ( $readsize = read ( $file->{filehandle}, $_, $file->{buffersize} ) ) {
push #imageData, unpack( 'S' x ( $readsize/($file->{fileProperties}->{bitsAllocated}/8) ), $_ );
}
print "DEBUG: found ", (scalar #imageData), " elements\n";
gives me this output:
Can't use string ("0") as a HASH ref while "strict refs" in use at /root/test.pl line 9.
DEBUG: found 5638203 elements
if I comment out the 'use strict' line, I get:
Use of uninitialized value in ref-to-glob cast at /root/test.pl line 9.
Use of uninitialized value in read at /root/test.pl line 9.
read() on unopened filehandle at /root/test.pl line 9.
DEBUG: found 5638215 elements
I, uh, obviously...don't understand this....
You need to return a reference to your file handle:
return {
'fileProperties' => \%fileProperties,
'filehandle' => \*FH, # <--- reference.
'buffersize' => $buffersize,
};
And then when reading, you can work directly on the filehandle, you don't have to transfer it to a fileglob:
# local *FH = $file->{filehandle}; <--- Not Needed. Below, just use the lexical fh
while ( read($file->{filehandle}, $_, $file->{buffersize}) ) {
If you work with lexical file handles from the very beginning, it becomes a lot more obvious how to pass them:
open my $fh, '<', 'myfile.txt' or die "Can't open: $!";
return {
'fileProperties' => \%fileProperties,
'filehandle' => $fh,
'buffersize' => $buffersize,
};

parse all arguments and store to hash

How can i parse all the arbitrary arguments to a hash without specifying the argument names inside my perl script.
Running command with below argument should give hash like below.
-arg1=first --arg2=second -arg3 -arg4=2.0013 -arg5=100
{
'arg2' => 'second',
'arg1' => 'first',
'arg4' => '2.0013',
'arg3' => 1,
'arg5' => 100
};
This can be achieved using Getopt::Long as below
GetOptions(\%hash,
"arg1=s",
"arg2=s",
"arg3",
"arg4=f",
"arg5=i");
However, my argument list is too long and i don't want to specify argument names in GetOptions.
So a call to GetOptions with only hash as a parameter should figure out what arguments are (and their type integer/string/floats/lone arguments) and just create a hash.
There are a lot of Getopt modules. The following are some that will just slurp everything into a hash like you desire:
Getopt::Mini
Getopt::Whatever
Getopt::Casual
I personally would never do something like this though, and have no real world experience with any of these modules. I'd always aim to validate every script for both error checking and as a means to self-document what the script is doing and uses.
Try this:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
sub getOptions {
my (%opts, #args);
while (#_) {
my $opt = shift;
if ($opt =~ /^-/) {
if ($opt =~ /-+([^=]+)(?:=(.+))?/) {
$opts{$1} = $2 ? $2 : 1;
}
}
else {
push #args, $opt;
}
}
return (\%opts, \#args);
}
my ($opts, $args) = getOptions(#ARGV);
print Dumper($opts, $args);
Testing:
$ perl t.pl -arg1=first --arg2=second -arg3 -arg4=2.0013 -arg5=100 datafile
$VAR1 = {
'arg2' => 'second',
'arg1' => 'first',
'arg4' => '2.0013',
'arg3' => 1,
'arg5' => '100'
};
$VAR2 = [
'datafile'
];
This will work as expected for your example,
my %hash = map { s/^-+//; /=/ ? split(/=/, $_, 2) : ($_ =>1) } #ARGV;

Perl Getopt::Long Assigning variable then going to subroutine

I have the following piece of code
my $use = "Use: " . basename($0) . " [options]";
my $version = "Version: 0.1 \n";
my $variableA;
my $variableB;
GetOptions(
'a=s' => \$variableA,
'help' => sub { print $use; exit 0 },
'version' => sub { print $version; exit 0 },
'b' => sub { \$variableB, &this_subroutine; goto NOWGOHERE; },
);
die "Incorrect use. \n" unless (defined $variableA || defined $variableB);
sub this_subroutine {
print "$variableB\n";
}
NOWGOHERE: print "HELLO I'M NOW HERE\n";
What I am trying to do is set $variableB and then do the &this_subroutine and the goto NOWGOHERE but I can only get it to do one or the other, not both, using either 'b=s' => \$variableB, or sub { &this_subroutine; goto NOWGOHERE;0 },
When trying to do both I cannot seem to print the $variableB, is there something obvious I am missing or doing wrong syntactically?
Using 'b=s' => \$variableB, sub { &this_subroutine; goto NOWGOHERE; }, does not seem to work either?
your help is much appreciated, many thanks
$variableB will never have a value because you never assign to it.
'a=s' => \$variableA,
gives $variableA a value because, when Getopt::Long is given a scalar ref, it assigns the option's value to that scalar.
On the other hand,
'b' => sub { \$variableB, &this_subroutine; goto NOWGOHERE; },
gives Getopt::Long a code reference, which it can't assign the option value to.
Based on the docs, it appears that it passes the option name and option value to the coderef as parameters, in which case
'b=s' => sub { $variableB = $_[1]; this_subroutine(); goto NOWGOHERE; },
should probably do what you want.

troubleshooting "pseudo-hashes are deprecated" while using xml module

I am just learning how to use perl hashes and ran into this message in perl. I am using XML::Simple to parse xml output and using exists to check on the hash keys.
Message:
Pseudo-hashes are deprecated at ./h2.pl line 53.
Argument "\x{2f}\x{70}..." isn't numeric in exists at ./h2.pl line 53.
Bad index while coercing array into hash at ./h2.pl line 53.
I had the script working earlier with one test directory and then executed the script on another directory for testing when I got this message. How do I resolve/workaround this?
Code that the error references:
use strict;
use warnings;
use XML::Simple;
use Data::Dumper;
#my $data = XMLin($xml);
my $data = XMLin($xml, ForceArray => [qw (file) ]);
my $size=0;
if (exists $data->{class}
and $data->{class}=~ /FileNotFound/) {
print "The directory: $Path does not exist\n";
exit;
} elsif (exists $data->{file}->{path}
and $data->{file}->{path} =~/test-out-00/) {
$size=$data->{file}->{size};
if ($size < 1024000) {
print "FILE SIZE:$size BYTES\n";
exit;
}
} else {
exit;
}
print Dumper( $data );
Working test case, data structure looks like this:
$VAR1 = {
'recursive' => 'no',
'version' => '0.20.202.1.1101050227',
'time' => '2011-09-30T02:49:39+0000',
'filter' => '.*',
'file' => {
'owner' => 'test_act',
'replication' => '3',
'blocksize' => '134217728',
'permission' => '-rw-------',
'path' => '/source/feeds/customer/test/test-out-00',
'modified' => '2011-09-30T02:48:41+0000',
'size' => '135860644',
'group' => '',
'accesstime' => '2011-09-30T02:48:41+0000'
'modified' => '2011-09-30T02:48:41+0000'
},
'exclude' => ''
};
recursive:no
version:0.20.202.1.1101050227
time:2011-10-01T07:06:16+0000
filter:.*
file:HASH(0x84c83ec)
path:/source/feeds/customer/test
directory:HASH(0x84c75d8)
exclude:
Data structure with seeing error:
$VAR1 = {
'recursive' => 'no',
'version' => '0.20.202.1.1101050227',
'time' => '2011-10-03T04:49:36+0000',
'filter' => '.*',
'file' => [
{
'owner' => 'test_act',
'replication' => '3',
'blocksize' => '134217728',
'permission' => '-rw-------',
'path' => '/source/feeds/customer/test/20110531/test-out-00',
'modified' => '2011-10-03T04:47:46+0000',
'size' => '121406618',
'group' => 'feeds',
'accesstime' => '2011-10-03T04:47:46+0000'
},
Test xml file:
<?xml version="1.0" encoding="UTF-8"?><listing time="2011-10-03T04:49:36+0000" recursive="no" path="/source/feeds/customer/test/20110531" exclude="" filter=".*" version="0.20.202.1.1101050227"><directory path="/source/feeds/customer/test/20110531" modified="2011-10-03T04:48:19+0000" accesstime="1970-01-01T00:00:00+0000" permission="drwx------" owner="test_act" group="feeds"/><file path="/source/feeds/customer/test/20110531/test-out-00" modified="2011-10-03T04:47:46+0000" accesstime="2011-10-03T04:47:46+0000" size="121406618" replication="3" blocksize="134217728" permission="-rw-------" owner="test_act" group="feeds"/><file path="/source/feeds/customer/test/20110531/test-out-01" modified="2011-10-03T04:48:04+0000" accesstime="2011-10-03T04:48:04+0000" size="127528522" replication="3" blocksize="134217728" permission="-rw-------" owner="test_act" group="feeds"/><file path="/source/feeds/customer/test/20110531/test-out-02" modified="2011-10-03T04:48:19+0000" accesstime="2011-10-03T04:48:19+0000" size="125452919" replication="3" blocksize="134217728" permission="-rw-------" owner="test_act" group="feeds"/></listing>
The "Pseudo-hashes are deprecated" error means you're trying to access an array as a hash, which means that either $data->{file} or $data->{file}{path} is an arrayref.
You can check the data type by using print ref $data->{file}. The Data::Dumper module may also help you to see what is in your data structure (perhaps while setting $Data::Dumper::Maxdepth = N to limit the dump to N number of levels if the structure is big).
UPDATE
Now that you are using ForceArray, $data->{file} should always point to an arrayref, which may possibly have multiple references to path. Here is a modified segment of your code to handle that. But note that the logic of the if-then-exit conditions may have to change.
if (defined $data->{class} and $data->{class}=~ /FileNotFound/) {
print "The directory: $Path does not exist\n";
exit;
}
exit if ! defined $data->{file};
# filter the list for the first file entry named test-out-00
my ( $file ) = grep {
defined $_->{path} && $_->{path} =~ /test-out-00/
} #{ $data->{file} };
exit if ! defined $file;
$size = $file->{size};
if ($size < 1024000) {
print "FILE SIZE:$size BYTES\n";
exit;
}
When using XML::Simple, the ForceArray option is one of the most important to understand, especially in cases when your input data has nested elements that can occur 1 or more times. For example:
use XML::Simple;
use Data::Dumper;
my #xml_snippets = (
'<opt> <name x="3" y="4">B</name> <name x="5" y="6">C</name> </opt>',
'<opt> <name x="1" y="2">A</name> </opt>',
);
for my $xs (#xml_snippets){
my $data = XMLin($xs, ForceArray => 0);
print Dumper($data);
}
Output:
$VAR1 = {
'name' => [ # Array ref because there are 2 <name> elements.
{
'y' => '4',
'content' => 'B',
'x' => '3'
},
{
'y' => '6',
'content' => 'C',
'x' => '5'
}
]
};
$VAR1 = {
'name' => { # No intermediate array ref.
'y' => '2',
'content' => 'A',
'x' => '1'
}
};
By activating the ForceArray option, you can direct XML::Simple to produce consistent data structures that always use the intermediate array reference, even when there is only 1 of a particular nested element. You can activate the option globally or for specific tags, as illustrated here:
my $data = XMLin($xs, ForceArray => 1 ); # Globally.
my $data = XMLin($xs, ForceArray => [qw(name foo bar)]);
First, I recommend that you use ForceArray => [qw( file )] as previously discussed. That will cause an array to be returned for file, whether there's one or more file element. This is easier to handle than having two possible formats.
As I previously indicated, the problem is that you made no provision for looping over multiple file elements. You said you wanted to exit if the file doesn't exist, so that means you want
my $found;
for my $file (#{ $data->{file} }) {
if ($file->{path} =~ m{/test-out-00\z}) {
$found = $file;
last;
}
}
die("Test file not found\n") if !$found;
... do something with file data in $found ...