Perl warning: Use of uninitialized value in join or string
Asked Answered
R

1

9

I'm getting:

"Use of uninitialized value in join or string at ./test_script.pl line 69, <fh> line 91."

The code creating this warning is here:

# Write results of SQL execution to output file
if ($@) { print "\n DB Error : ", $@; exit; }
open(FH, ">$output_file") or 
        die "\n cannot write to the file $output_file. Check the file permissions";
while(my @row= $sth->fetchrow_array) {
      print FH join($delimiter, @row). "\n";
}

It is coming from the "print ... join ...." line in the "while" block, but I'm not sure 100% on what is causing it and how to rectify it.

The $delimiter is initialized earlier as my $delimiter = "|" and the @row is getting its values from the $sth operation in the previous line.

Is this a "timing" issue regarding the @row values or is it something in the actual SQL query file being processed, as indicated by the "< fh> line 91" part of the warning? Any ideas on how I can avoid the warnings? BTW, the script works fine, it just generates a warning for each @row value fetched, which creates a huge amount of warnings on a large data set pull...

Resiniferous answered 6/11, 2014 at 17:27 Comment(3)
what does your query look like or the code which generated the query?Refresh
If I were to guess, one of the columns in @row is NULL.Chemurgy
add a statement before print and display values for bothPartible
H
16

@row may contain some elements that have an undefined value, probably because one or more of the columns in the database that populated @row was NULL. The behavior can be mimicked while still using join in a simple Perl one-liner like this:

perl -we 'my @array = ("Hello",undef,"world!"); print join("|",@array) . "\n";'

The output that will produce is:

Use of uninitialized value $array[1] in join or string at -e line 1.
Hello||world!

The reason this happens is because join is trying to join a value that doesn't stringify without being upgraded from "undefined" to "defined". Stringifying undef produces this warning message to alert you that you may be doing something unintentional, and incorrect.

An even simpler example could be this:

$ perl -we 'my $scalar; print "$scalar\n";'
Use of uninitialized value $scalar in concatenation (.) or string at -e line 1.

...or...

perl -wE 'say "" . undef'

Again, we get the warning because we're interpolating an undefined value into a string (in the first case), or forcing stringification (in the second case). Interpolation causes stringification, and stringification of an undefined value generally throws a warning to let you know you may have made a mistake.

If you don't mind silently upgrading the undef's to an empty string, you could do this:

print FH join( $delimiter, map { defined ? $_ : '' } @row ) . "\n";

This pre-processes @row before handing it to join(). For each element in @row, if the element has an undefined value, that value is replaced by an empty string, which is defined but visually quiet.

The other possibility is that $delimiter is undefined. Test for that by printing it, and if that turns out to be the case, the steps you will need to take to fix it are to simply make sure that it actually contains a value.

Simply silencing the warnings in a tight lexical scope could be ok as well, though from a philosophical standpoint, one method removes the cause, and the other removes one of the symptoms. The "no warnings 'uninitialized'; method described elsewhere does just that; silences the warning. You would do this when you know you're doing something perfectly Ok, but you also know that Perl is going to warn you about it because many times it's a mistake. This could be such a situation; you may be perfectly happy with just letting silent stringification of an undefined value happen inside of the join. And if that's the case, go ahead and squelch the warning. The most important thing is that you know what it means, verify what is causing it, and then make an informed decision as to what to do about it.

Heathcote answered 6/11, 2014 at 18:10 Comment(4)
In case anyone finds this, use of defined is ambiguous as shown. it needs to be map { defined $_ ? $_ : '' }Weixel
perl sees no ambiguity here, and the fact that defined falls back to using $_ as its parameter in the absence of other parameters is discussed in perldoc -f defined. The use of the ternary operator isn't ambiguous. And it's documented that map populates $_ as an alias for the list element it is iterating on. This comes down to a style issue, possibly mentioned somewhere in PBP.Heathcote
And in fact, yes, it's a PBP issue rolled into the Iterator Variables heading, page 105-107. It doesn't appear that this PBP suggestion cites ambiguity, but rather readability. And if readability were the concern, I would not disagree.Heathcote
Just as a comment, starting from Perl 5.10 map { defined ? $_ : '' } can be shortened down to map { $_ // '' } using logical defined-or operator.Jauregui

© 2022 - 2024 — McMap. All rights reserved.