octopusgrabbus
octopusgrabbus

Reputation: 10695

Why does Perl string become undefined?

What is the proper way to concatenate several scalar values into one Perl string?

The following code is deliberately a series of statements for debugging reasons.

my $bill_record;

$bill_record = $acct_no . " |";

$bill_record = $bill_record . defined($w_ptWtrMtrRecRef->{"mtr_addr_no"})  ?  $w_ptWtrMtrRecRef->{"mtr_addr_no"} : " "  . " |" ;

$bill_record = $bill_record . defined($w_ptWtrMtrRecRef->{"mtr_addr_str"}) ? $w_ptWtrMtrRecRef->{"mtr_addr_str"} : " " . " |" ;

$bill_record = $bill_record . defined($w_ptWtrMtrRecRef->{"mtr_addr_apt"}) ? $w_ptWtrMtrRecRef->{"mtr_addr_apt"} : " " . " |"  ;

$bill_record = $bill_record . $issue_date . " |";

The | character is serving as a delimiter. Each line will be '\n terminated.

After the last line $bill_record = $bill_record . $issue_date . " |"; This error appears:

Use of uninitialized value $bill_record in concatenation (.) or string at /home/ics/include/WsBillFunc.pm line 1022.
 at /home/ics/include/WsBillFunc.pm line 1022

$issue_date is defined when assigned.

What could be causing $bill_record to become undefined, and what is the proper way to concatenate a bunch of scalar values into one string?

Upvotes: 2

Views: 185

Answers (2)

brian d foy
brian d foy

Reputation: 132812

I'd probably do that in one statement with a join:

$bill_record = join ' |',
    map( {
        defined( $_ ) ? $_ : ' '
        } @{ $w_ptWtrMtrRecRef }{ qw( mtr_addr_no mtr_addr_str mtr_addr_apt ) }
        ),
    $issue_date,
    '';

In the map I limit with parens because I only want to apply it to the hash slice. After that is the $issue_date and the empty string. That empty string gets the final | you have.

But, for your problem, it looks like you have a precedence problem. One way to see this is to ask Perl to compile then deparse your program to see what it thinks you wanted. The B::Deparse module does this and I use the -p argument to add extra parentheses.

Here's a cut down version of your original program with the added call to the deparser at the top (it's the B::Deparse module but the namespace is O:

#!/usr/bin/perl

use O qw(Deparse -p);

my $b;
$b = $acct_no . " |";
$b = $b . defined($w->{"no"})  ? $w->{"no"}  : " " . " |" ;
$b = $b . defined($w->{"str"}) ? $w->{"str"} : " " . " |" ;
$b = $b . defined($w->{"apt"}) ? $w->{"apt"} : " " . " |"  ;
$b = $b . $issue_date . " |";

It outputs:

my($b);
($b = ($acct_no . ' |'));
($b = (($b . defined($$w{'no'})) ? $$w{'no'} : '  |'));
($b = (($b . defined($$w{'str'})) ? $$w{'str'} : '  |'));
($b = (($b . defined($$w{'apt'})) ? $$w{'apt'} : '  |'));
($b = (($b . $issue_date) . ' |'));

The key part is the (($b . defined($$w{'no'})). The current value of $b is concatenated with the return value of defined, then the conditional operator (? :) is done. If the test value is true, it returns the first value in the conditional.

When it gets to mgr_apt_no, there are probably many records that don't have that value set. However, the combined value of the previous $b and $$w{'apt'} is defined because $b is not empty. Thus, it chooses the value of $$w{'apt'} to assign to $b. When it does the last line, $b is empty for the concatenation with $issue_date.

Upvotes: 3

ajb
ajb

Reputation: 31699

I don't know specifically why $bill_record is undefined. But if I understand what you're trying to do, you're running into a precedence problem: the ?: operator has lower precedence than concatenation ., so that

$bill_record = $bill_record . defined($w_ptWtrMtrRecRef->{"mtr_addr_no"})  ?  $w_ptWtrMtrRecRef->{"mtr_addr_no"} : " "  . " |" ;

is treated as

$bill_record = ($bill_record . defined($w_ptWtrMtrRecRef->{"mtr_addr_no"}))  ?  $w_ptWtrMtrRecRef->{"mtr_addr_no"} : (" "  . " |") ;

which I suspect is not what you want. Try adding parentheses:

$bill_record = $bill_record . (defined($w_ptWtrMtrRecRef->{"mtr_addr_no"})  ?  $w_ptWtrMtrRecRef->{"mtr_addr_no"} : " ")  . " |" ;

(Or use .= as another commenter suggested.)

Upvotes: 6

Related Questions