eepstein has asked for the wisdom of the Perl Monks concerning the following question:

Using Perl v5.20.2 on OpenBSD 5.8.

I've written a management script called by a cron job that pulls values from a database. The ID of each server is represented by the hostname. During periods of network outages, or poor performance otherwise, I've receive the following warning via the cron job:

Use of uninitialized value $host in concatenation (.) or string at /etc/mrtg/testing.pl line 128.

Along with this warning I receive an error from DBI which indicates it couldn't make a connection. That is what leads me to believe it may be related to network errors and performance.

The cron job itself fires off twice a minute with two separate sets of parameters, so it is succeeding >90% of the time. The code itself is fairly simple, and writes status and event logs to /var/log/daemon. When line 128 is being run the daemon log is being updated by my management script. I would post those too, but my newsyslog is a bit aggressive (sorry). The log is being updated normally as we speak, with no errors.

# Perl libraries we need - Please note that Date::Parse is not # standard and needs to be installed as of OpenBSD 5.7 use utf8; use Path::Class; use strict; use warnings; use Time::Piece; use Date::Parse; use Socket; use Sys::Hostname; use POSIX qw(strftime); use DBI; use IO::Socket::INET; use Storable; use Storable qw(nstore store_fd nstore_fd freeze thaw dclone); use IPC::Open2; use Symbol 'gensym'; use String::Util 'trim'; use Net::SNMP; use Data::Dumper; # Get our hostname for C2 operations our $host = hostname(); # Get our local IP address by connecting to the MRTG Database our $localip = ''; sub get_local_ip(){ my $sock = IO::Socket::INET->new( PeerAddr=> $mrtgc2, PeerPort=> 5432, Proto => "tcp"); our $localip = ''; $::localip = $sock->sockhost; } get_local_ip(); ...... ...... ...... ## The following line is #128 from the above error ## print $fd $daemon_log_timestamp.' '.our $host.' mrtg[]:['.$log_entry_c +ategory.']: '.$log_entry.'.'."\n";
Anybody have an idea as to why the value would be uninitialized? Sys::Hostname uses many methods to get the hostname, so I'm surprised at the error, and stumped about the intermittent nature of it.

Replies are listed 'Best First'.
Re: Uninitialized value from hostname()
by stevieb (Canon) on Oct 11, 2016 at 23:23 UTC

    Are you sharing this variable with other modules? If not, replace our with my. Does this: perl MSys::Hostname -E 'say hostname();' return consistently?

    ...and this looks odd...

    print $fd $daemon_log_timestamp.' '.our $host.' mrtg[]:['.$log_entry_c +ategory.']: '.$log_entry.'.'."\n";

    Why are you using our $host here? That doesn't make any sense that I can see ;) Another thing, is that in double-quotes, Perl variables "interpolate":

    print $fd "$daemon_log_timestamp $host mrtg[]:[$log_entry_category]: $ +log_entry\n";

      That line of code is within a subroutine that writes to the daemon log. I'm also fairly new to Perl. I have experience with other languages, but Perl is... different :)

      # Get our hostname for logging and notifications our $host = hostname(); # Subroutine to append a new MRTG entry to the daemon log sub write_mrtg_log{ my( $arg1, $arg2 ) = @_; my $log_entry = defined $arg1 ? $arg1 : 'Empty Log Entry'; my $log_entry_category = defined $arg2 ? $arg2 : 'SCHEDULER'; my $daemon_log_timestamp = localtime()->strftime('%b %d %H:%M:%S'); open(my $fd, ">>/var/log/daemon"); print $fd $daemon_log_timestamp.' '.our $host.' mrtg[]: ['.$log_entr +y_category.']: '.$log_entry.'.'."\n"; close $fd; }

      I'm still trying to understand our, my, and local. I honestly don't understand why I need to put 'our' in front of a variable just because it is within an IF/ELSE block. I can understand isolating a subroutine or referenced module, but having it also for IF/ELSE, WHILE, etc. is confusing me. At this point it seems like 'our' creates a global? Or is it preferred to just pass everything as arguments to subroutines?

      Yes, the command you gave me (it needed a hyphen?) is returning the hostname consistently. The daemon log entries also use the value and they have it filled in most of the time. The exceptions are during network outages, which I've no idea how they're related yet.

      Thanks for the suggestions on cleaning up the syntax. I'm still learning how to write Perl well, and I appreciate any advice.

Re: Uninitialized value from hostname()
by Monk::Thomas (Friar) on Oct 12, 2016 at 08:21 UTC
    I also find your usage of 'our' to be quite strange. Please try stevieb's advise to see if your error goes away by using 'my $host = ...'. Additionally I recommend to rewrite your get_local_ip function as follows:

    minimum - use return value to provide 'local ip'
    sub get_local_ip { my $sock = IO::Socket::INET->new( PeerAddr=> $mrtgc2, PeerPort=> 5432, Proto => "tcp"); return $sock->sockhost; } my $localip = get_local_ip();
    refinement - with extracted parameters:
    sub get_local_ip { my $host = shift; my $port = shift; my $sock = IO::Socket::INET->new( PeerAddr=> $host, PeerPort=> $port, Proto => "tcp"); return $sock->sockhost; } my $localip = get_local_ip($mrtgc2, 5432);
      I also find your usage of 'our' to be quite strange.
      I'm new to Perl, so I'm sure some of it is going to look "strange" :)

      Thank you for the rewrite. I'm still having difficulty understanding our, my, and local. If I change it from 'our' to 'my', how do I properly "access" $localip from within nested IF/ELSE and WHILE? That in a nutshell is my biggest challenge with Perl currently, is watching the warnings and errors complain about my syntax for using variables.

      Verifying whether this has worked or not will take some time. This script is running out in the field, and I would need to wait for another network event to see what happens. Under normal conditions I have no warnings or errors returned from running the script. It also runs on many other systems and returns no errors or warnings at all. Since it's all logged I can verify what occurs on the other systems and that the script is working.

        long answer: Variable Scoping in Perl: the basics

        short answer:
        • forget about 'our' and 'local' (for now)
        • identify the relevant blocks (= something enclosed in '{ }')
        • if you need the variable outside of the block, define it outside

        minimalistic example to set a value inside a conditional / loop / block:
        (I intentionally used the variable '$value' inside the block to demonstrate 'my'. It is not mandatory, you can also assign directly.)
        my $variable; if (1 == 1) { my $value = 'a'; $variable = $value; } else { my $value = 'b'; $variable = $value; } # => '$variable' now contains 'a'