94

I currently use the following Perl to check if a variable is defined and contains text. I have to check defined first to avoid an 'uninitialized value' warning:

if (defined $name && length $name > 0) {
    # do something with $name
}

Is there a better (presumably more concise) way to write this?

brian d foy
  • 129,424
  • 31
  • 207
  • 592
Jessica
  • 1,421
  • 5
  • 15
  • 10

9 Answers9

89

You often see the check for definedness so you don't have to deal with the warning for using an undef value (and in Perl 5.10 it tells you the offending variable):

 Use of uninitialized value $name in ...

So, to get around this warning, people come up with all sorts of code, and that code starts to look like an important part of the solution rather than the bubble gum and duct tape that it is. Sometimes, it's better to show what you are doing by explicitly turning off the warning that you are trying to avoid:

 {
 no warnings 'uninitialized';

 if( length $name ) {
      ...
      }
 }

In other cases, using some sort of null value instead of the actual data gets around the problem. With Perl 5.10's defined-or operator, give length an explicit empty string (defined, and gives back zero length) instead of the variable that would trigger the warning:

 use 5.010;

 if( length( $name // '' ) ) {
      ...
      }

In Perl 5.12, it's a bit easier because length on an undefined value also returns undefined. That might seem like a bit of silliness, but that pleases the mathematician I might have wanted to be. That doesn't issue a warning, which is the reason this question exists.

use 5.012;
use warnings;

my $name;

if( length $name ) { # no warning
    ...
    }
brian d foy
  • 129,424
  • 31
  • 207
  • 592
  • 6
    Also, in v5.12 and later, `length undef` returns undef, instead of warning and returning 0. In boolean context, undef is just as false as 0, so if you're targeting v5.12 or later, you can just write `if (length $name) { ... }` – rjbs Jul 09 '14 at 17:14
26

As mobrule indicates, you could use the following instead for a small savings:

if (defined $name && $name ne '') {
    # do something with $name
}

You could ditch the defined check and get something even shorter, e.g.:

if ($name ne '') {
    # do something with $name
}

But in the case where $name is not defined, although the logic flow will work just as intended, if you are using warnings (and you should be), then you'll get the following admonishment:

Use of uninitialized value in string ne

So, if there's a chance that $name might not be defined, you really do need to check for definedness first and foremost in order to avoid that warning. As Sinan Ünür points out, you can use Scalar::MoreUtils to get code that does exactly that (checks for definedness, then checks for zero length) out of the box, via the empty() method:

use Scalar::MoreUtils qw(empty);
if(not empty($name)) {
    # do something with $name 
}
Adam Bellaire
  • 108,003
  • 19
  • 148
  • 163
17

First, since length always returns a non-negative number,

if ( length $name )

and

if ( length $name > 0 )

are equivalent.

If you are OK with replacing an undefined value with an empty string, you can use Perl 5.10's //= operator which assigns the RHS to the LHS unless the LHS is defined:

#!/usr/bin/perl

use feature qw( say );
use strict; use warnings;

my $name;

say 'nonempty' if length($name //= '');
say "'$name'";

Note the absence of warnings about an uninitialized variable as $name is assigned the empty string if it is undefined.

However, if you do not want to depend on 5.10 being installed, use the functions provided by Scalar::MoreUtils. For example, the above can be written as:

#!/usr/bin/perl

use strict; use warnings;

use Scalar::MoreUtils qw( define );

my $name;

print "nonempty\n" if length($name = define $name);
print "'$name'\n";

If you don't want to clobber $name, use default.

Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
5

In cases where I don't care whether the variable is undef or equal to '', I usually summarize it as:

$name = "" unless defined $name;
if($name ne '') {
  # do something with $name
}
Gaurav
  • 1,888
  • 1
  • 18
  • 23
  • In Perl 5.10, this can be shortened to `$name //= "";` which is exactly what Sinan posted. – Chris Lutz Sep 28 '09 at 06:31
  • And even if you don't have perl 5.10, you can still write `$name ||= "";` – RET Sep 28 '09 at 07:16
  • 1
    @RET: you can't use the || operator here since it replaces the string '0' with ''. You have to check if it is defined, not true. – brian d foy Sep 29 '09 at 04:25
  • Chris, RET: Yup, I know. I was specifically trying to suggest that if Jessica was not concerned with the difference between `undef` and `""`, she should just change one to the other and use a single test. This won't work in the general case, for which the other solutions posted are way better, but in this specific case leads to neat code. Should I rephrase my answer to make this clearer? – Gaurav Sep 29 '09 at 04:27
1

You could say

 $name ne ""

instead of

 length $name > 0
mob
  • 117,087
  • 18
  • 149
  • 283
  • 7
    This will still give you a warning. The reason people check definedness first is to avoid the 'uninitialized value' warning. – brian d foy Sep 26 '09 at 19:20
1

It isn't always possible to do repetitive things in a simple and elegant way.

Just do what you always do when you have common code that gets replicated across many projects:

Search CPAN, someone may have already the code for you. For this issue I found Scalar::MoreUtils.

If you don't find something you like on CPAN, make a module and put the code in a subroutine:

package My::String::Util;
use strict;
use warnings;
our @ISA = qw( Exporter );
our @EXPORT = ();
our @EXPORT_OK = qw( is_nonempty);

use Carp  qw(croak);

sub is_nonempty ($) {
    croak "is_nonempty() requires an argument" 
        unless @_ == 1;

    no warnings 'uninitialized';

    return( defined $_[0] and length $_[0] != 0 );
}

1;

=head1 BOILERPLATE POD

blah blah blah

=head3 is_nonempty

Returns true if the argument is defined and has non-zero length.    

More boilerplate POD.

=cut

Then in your code call it:

use My::String::Util qw( is_nonempty );

if ( is_nonempty $name ) {
    # do something with $name
}

Or if you object to prototypes and don't object to the extra parens, skip the prototype in the module, and call it like: is_nonempty($name).

daotoad
  • 26,689
  • 7
  • 59
  • 100
  • 2
    Isn't this like using a hammer to kill a fly? – Zoran Simic Sep 26 '09 at 04:03
  • 4
    @Zoran No. Factoring code out like this beats having a complicated condition replicated in many different places. That would be like using pinpricks to kill an elephant. @daotoad: I think you should shorten your answer to emphasize the use of `Scalar::MoreUtils`. – Sinan Ünür Sep 26 '09 at 07:05
  • @Zoran: Scalar::MoreUtils is a very lightweight module with no dependencies. Its semantics are also well known. Unless you are allergic to CPAN, there's not much reason to avoid using it. – Adam Bellaire Sep 26 '09 at 11:45
  • @daotoad - Since you've prototyped your `is_nonempty` to `($)` Perl will warn _at compile time_ when the function is called with the wrong number of arguments. You shouldn't need to do that at runtime. – Chris Lutz Sep 28 '09 at 06:14
  • 1
    @Chris Lutz, yeah, I shouldn't. But prototypes are semi-broken--there are easy ways to break prototype enforcement. For example, crappy and/or outdated tutorials continue to encourage the use of the `&` sigil when calling functions. So I tend not to rely on prototypes to do all the work. I suppose I could add "and quit using the & sigil on sub calls unless you really mean it" to the error message. – daotoad Sep 28 '09 at 07:35
  • 1
    It's easier to think about prototypes as hints to the perl compiler so it knows how to parse something. They aren't there to validate arguments. They may be broken in terms of people's expectations, but so many things are. :) – brian d foy Sep 28 '09 at 18:52
  • 1
    The OP asked for a **concise** way to do it, and in this case there is one. Less code is usuaully better than more code, as long as it's understandable. This solution is simply unnecessary. – Will Sheppard Nov 09 '17 at 15:37
  • @WillSheppard, `isnonempty $foo` is concise. Heck, I added a prototype so you don't have to type the parenthesis. It hides complexity of the check inside a function. In this case, a function defined in a library (ideally one that's already written). Some things can never be concise unless you add abstraction. Adding abstraction adds some overhead, so if you are avoiding typing something out only 2 or 3 times, it's not such a good deal. But if you are doing `is_empty` checks all over the place, it is a huge win to simplify all your checks AND keep them consistent at the same time. – daotoad Jan 03 '18 at 00:27
  • I agree a library should be used (I now realise I also missed your reference to [Scalar::MoreUtils::empty](https://metacpan.org/pod/release/RKRIMEN/Scalar-MoreUtils-0.02/lib/Scalar/MoreUtils.pm#empty-VALUE)) because your answer quickly skims over the CPAN module and recommends a different custom solution, which I still say is unnecessary, and inferior to the CPAN solution because it has not received the benefit of CPAN testing. – Will Sheppard Jan 05 '18 at 11:44
  • @Will Sheppard, sadly, many people find themselves at the mercy of an employer who bans use of CPAN or whose dependency management process is so byzantine that it is actually preferable to DIY for simple things rather than struggling to install them. That's why I provide some example code for how to do it without CPAN. – daotoad Jan 18 '18 at 00:56
0

The excellent library Type::Tiny provides an framework with which to build type-checking into your Perl code. What I show here is only the thinnest tip of the iceberg and is using Type::Tiny in the most simplistic and manual way.

Be sure to check out the Type::Tiny::Manual for more information.

use Types::Common::String qw< NonEmptyStr >;

if ( NonEmptyStr->check($name) ) {
    # Do something here.
}

NonEmptyStr->($name);  # Throw an exception if validation fails
daotoad
  • 26,689
  • 7
  • 59
  • 100
-2

How about

if (length ($name || '')) {
  # do something with $name
}

This isn't quite equivalent to your original version, as it will also return false if $name is the numeric value 0 or the string '0', but will behave the same in all other cases.

In perl 5.10 (or later), the appropriate approach would be to use the defined-or operator instead:

use feature ':5.10';
if (length ($name // '')) {
  # do something with $name
}

This will decide what to get the length of based on whether $name is defined, rather than whether it's true, so 0/'0' will handle those cases correctly, but it requires a more recent version of perl than many people have available.

Dave Sherohman
  • 45,363
  • 14
  • 64
  • 102
  • 2
    Why lead off with a broken solution only to say that it is broken? – brian d foy Sep 26 '09 at 19:17
  • Because, as I also mentioned, 5.10 is "a more recent version of perl than many people have available." YMMV, but "this is a 99% solution that I know you can use, but there's a better one that maybe you can use, maybe you can't" seems better to me than "here's the perfect solution, but you probably can't use it, so here's an alternative you can probably get by with as a fallback." – Dave Sherohman Sep 26 '09 at 22:20
  • 1
    Even with earlier perls you can have a working solution instead of a broken one. – brian d foy Sep 28 '09 at 06:09
-3
if ($name )
{
    #since undef and '' both evaluate to false 
    #this should work only when string is defined and non-empty...
    #unless you're expecting someting like $name="0" which is false.
    #notice though that $name="00" is not false
}
Joseph
  • 2,153
  • 1
  • 12
  • 3