Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Using number "0" as an input argument

by Doozer (Scribe)
on Sep 22, 2015 at 08:39 UTC ( [id://1142708]=perlquestion: print w/replies, xml ) Need Help??

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

Hi All.

I'm after some help on dealing with a valid number "0" as an input argument. I have a script which takes input arguments. One of the arguments relates to a USB audio input device number on a Raspberry Pi. The device numbers start from 0 and increment depending on how many of these devices are installed.

I know I can validate that the input argument was there via the following bit of code:

chomp(my $in1 = $ARGV[0] || ''); if (defined $in1) { #Validate; }

The problem I have is I also want to validate that the input is only a number. If I do regex pattern match validation against $in1 when it is "0", Perl gives me the uninitialized value error. I am aware that 0 translates to false.

How can I use "0" as a valid number? I have tried looking on Google for an answer but have been unsuccessful.

Replies are listed 'Best First'.
Re: Using number "0" as an input argument
by Eily (Monsignor) on Sep 22, 2015 at 08:57 UTC

    Actually you should check that the argument is defined instead of its truth, you can do either:
    my $in1 = defined $ARGV[0] ? $ARGV[0] : ''; or my $in1 = $ARGV[0] // '';, I personally prefer to check that the number of arguments is correct first : my $in = (@ARGV > 0) ? shift : ''; (actually I often check the number of arguments first, so that I can ouput a warning when needed)
    Note that after this point, $in1 will always be defined, because an undefined value will be replaced by the empty string (but 0 will be kept as is, and not turned to the empty string as in your code).

    I don't know what you do exactly of your input, but I'm not sure you actually need the empty string when your argument is invalid, undef seems to be what you expect. There's of course more than one way to do it, so feel free to adapt this to your needs with a ternary operator, statment modifier or whatever will make this look better to you:

    my $input = undef; if (@ARGV > 0 && $ARGV[0] !~ /\D/) { $input = shift; }

    Edit: s/expecy/expect/ as pointed out by MidLifeXis :)

      Sorry I should have given more info.

      The script takes 2 input arguments, both of which are numbers. The second argument is a time period in seconds so 0 will be an invalid choice.

      The first input argument which is the one that CAN be 0 is later used in the script as part of a system command input option (-Dhw:$in1).

      If by "number" you mean an integer device number, the following technique for getting a list of results for regex captures should help. It is documented among other places here: http://perldoc.perl.org/perlrequick.html#Extracting-matches

      use warnings; use strict; my ($device) = defined($ARGV[0]) ? $ARGV[0] =~ /^\s*(\d+)\s*$/ : ''; print "Device #$device\n";
      Ron
Re: Using number "0" as an input argument
by Corion (Patriarch) on Sep 22, 2015 at 09:26 UTC

    I know I can validate that the input argument was there via the following bit of code:

    chomp(my $in1 = $ARGV[0] || ''); if (defined $in1) { #Validate; }

    Have you ever encountered a case where $in1 was not defined?

    Maybe you want to directly check the number of arguments in @ARGV or look at what the || operator does?

    Maybe the following code helps you analyze what happens better:

    my $in1 = $ARGV[0] || 'user-did-not-pass-an-argument'); print $in1;

    Run the above code with

    perl -w myscript.pl # no argument perl -w myscript.pl "" # empty argument perl -w myscript.pl 0 # zero argument perl -w myscript.pl something-else # other argument

      Thanks for this. I just noticed I put "||" in my post instead of "//" which is what I have in my script. I know how they differ but thanks for pointing it out.

      I have now figured out it was a combination of using the // operator and also chomp. The code now looks something like:

      my $in1 = $ARGV[0]; chomp(my $in2 = $ARGV[1] // ''); if (defined $in1) { $in1 =~ s/\s+//g; # Remove all whitespace instead of chomp if ($in1 =~ /\D+/) { print "Input invalid. Only numbers are allowed\n"; } } else { print "No valid input given\n"; }
        $in1 =~ s/\s+//g;

        Although your comment suggests you are aware, I will still point out that s/\s+//g will remove all spaces, including spaces between digits/other characters, not just leading and trailing spaces.

        While unlikely to be a problem with your program, it is still possible to feed an unexpected value:

        yourprog "1 2"

        would result in $in1 being 12 and $in2 being empty

        This is why you should copy/paste code instead of retyping it in your post.

        Both more accurate and faster as well! :)

Re: Using number "0" as an input argument
by karlgoethebier (Abbot) on Sep 22, 2015 at 10:15 UTC

    I guess you want something like this:

    #!/usr/bin/env perl use strict; use warnings; use Getopt::Long; use Pod::Usage; use Data::Dump; my %options; # Getopt::Long::Configure("no_ignore_case"); # perhaps you need it... GetOptions( \%options, "device=i" ); pod2usage( -exitstatus => 0, -verbose => 2 ) unless defined $options{d +evice}; dd \%options; # stuff... __END__ =pod =head1 USAGE Something went wrong. =cut

    Please see also Re: throw a warning if no argument were passed, Re: GetOpt Organization and Getopt::Long

    Regards, Karl

    «The Crux of the Biscuit is the Apostrophe»

Re: Using number "0" as an input argument
by BillKSmith (Monsignor) on Sep 22, 2015 at 13:01 UTC
    An incorrect number of arguments is a very different error than an incorrectly formatted option. Report it separately.
    use strict; use warnings; # More Realistic values are required for these constants. my $VALID_DATE_FORMAT = qr{\d\d/\d\d/\d\d\d\d}; my $MAX_DEVICE_NUMBER = 17; if (!@ARGV or @ARGV>2){ die "Usage:...\n"; } my ($device, $date) = @ARGV; $device =~ s/\s//g; $date //= q(); if ($device =~ m/\D/ or $device < 0 or $device > $MAX_DEVICE_NUMBER) +{ warn "Invalid device number - Must be an integer < $MAX_DEVICE_NUM +BER\n"; } if ($date and $date !~ /$VALID_DATE_FORMAT/) { warn "Invalid date - Must be mm/dd/yyyy\n"; } print "device: $device\ndate: $date\n";
    Bill
Re: Using number "0" as an input argument
by Anonymous Monk on Sep 22, 2015 at 08:58 UTC
    Yeah, only runnable code that proves the complaint proves it, so counter proof
    use strict; use warnings; my $foo; my $bar = 0; my $baz = '0'; for my $arg ( $baz, $bar, $foo ){ use Data::Dump; dd( $arg ); print qq{ the $arg\n }; } __END__ 0 the 0 0 the 0 undef Use of uninitialized value $arg in concatenation (.) or string at - li +ne 9. the

    So there you have it 0 is a perlnumber, your problem is that you don't have 0, you have under

Re: Using number "0" as an input argument
by sundialsvc4 (Abbot) on Sep 22, 2015 at 14:28 UTC

    Your problem is the part that says:   || ''.

    In a test for truthiness, zero is False.   As demonstrated by these two one-liners:

    perl -e 'my $foo="0"; print "\""; print ($foo || "X"); print "\" is th +e word.\n";' "X" is the word. perl -e 'my $foo=0; print "\""; print ($foo || "X"); print "\" is the +word.\n";' "X" is the word.

    Just to make the foregoing abundantly clear:   the difference between the two examples is that, in the first, $foo is a string, while in the second, it is integer.   Perl silently performs the data-type conversion, regarding both as “zero.”   In both cases, Perl concludes that the value, being zero, is False.   Therefore, the expression ($foo || "X") evaluates to its right-hand side:   “X.”   Which is definitely not what you wanted.

    I agree that you should be testing the number of arguments first, and die()ing with a message to the effect that the number is incorrect.   Then, if necessary, test the arguments against a regex pattern to be certain that each consists of one-or-more digits, giving another (different) message if this is not the case.   Do not test arguments for “truthiness” because, as you just found out, this relies on what the values actually are.   (Also do not rely on exists, because that does not care what they actually are.)

    Several years from now, your successor’s successor’s successor ... who is trying to debug a process of which your script is a tiny part (until s/he, too, can get-the-hell out of there) ... will thank you for your diligence and attention to detail.   ;-)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1142708]
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (6)
As of 2024-04-23 14:07 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found