Quit while loop that asks for numbers with IO::Prompter - perl

While using IO::Prompter I'm asking only numbers as input. This works. However I can't seem to find an elegant way to move away from the subroutine if I enter something like 'quit'.
In the documents it said something like:
while (my $cmd = prompt '>', -fail=>'quit') {
...
}
But I haven't been able to implement that and tried the following that doesn't function properly (I can't quit).
#!/usr/bin/perl
use strict;
use warnings;
use IO::Prompter;
my $ask = prompt "Do you want to show numbers?", -yn;
print "You entered: $ask\n";
if ( $ask eq 'y' ) {
showNumbers();
}
else {
print "You said: no\n";
}
sub showNumbers {
while ( prompt -num, 'Enter a number'){
print "$_\n";
}
}

-DEF can be used to provide a default that's not a valid response, allowing us to distinguish between a valid a response and just pressing Enter.
sub showNumbers {
while (1) {
my $num = prompt 'Enter a number', -num, -DEF => "";
# $num is a weird value that true even for an empty string, so
# we must separately check for false (meaning EOF) and empty string.
last if !$num || $num eq "";
print "$num\n";
}
}

Related

control flow validation in perl

This is my code which has switch statement works fine but if user enters greater than 3 or blank text then it should stay in first sub routine how can i do this in perl
use strict;
use warnings;
use Switch;
my $input = "Enter the number:";
sub input(){
print "Choose You Input Method"."\n";
print "1.UPC"."\n";
print "2.URL"."\n";
print "3.Elastic Search"."\n";
print $input;
$input = <>;
chomp($input);
switch($input){
case 1 {print "UPC"."\n"}
case 2 {print "URL"."\n"}
case 3 {print "Elastic Search"."\n"}
else {print "Enter the correct value"."\n"}
}
}
input();
my $pinput = "Enter the number:";
sub pinput(){
print "Choose Your Process Method"."\n";
print "1.API"."\n";
print "2.Mongo"."\n";
print $pinput;
$pinput = <>;
chomp($pinput);
switch($pinput){
case 1 {print "API"."\n"}
case 2 {print "MONGO"."\n"}
else {print "Enter the correct value"."\n"}
}
}
pinput();
if user enters something like 4 or blank data it should not pass to another sub routine it should stay on the same sub routine how can i do this?
Wrap the prompt code into a block with redo:
#!/usr/bin/perl
use warnings;
use strict;
use Switch::Plain;
PROMPT: {
chomp(my $input = <>);
nswitch ($input) {
case 1 : { print "UPC\n" }
case 2 : { print "URL\n" }
case 3 : { print "Elastic Search\n" }
default : { print "Enter the correct value\n" ; redo PROMPT }
}
}
I used Switch::Plain instead of Switch, as it is much safer (it doesn't use a source filter) and sufficient for your case.
perlfaq7 - How do I create a switch or case statement?
Using the builtin function since 5.10
use 5.010;
use strict;
use warnings;
PROMPT: {
chomp(my $input = <>);
given ( $input ) {
when( '1' ) { say "UPC" }
when( '2' ) { say "URL" }
when( '3' ) { say "Elastic Search" }
default { print "Enter the correct value"; redo PROMPT }
};
}

Perl 5.12.4, Switch not working?

I am new to perl, and am trying out code which uses a simple switch. The school server runs only Perl 5.12.4, so I am coding accordingly.
The issue I am having is that the variable controlling the switch will not throw any of the cases, no matter how I describe them, and is always falling through to the default case.
Ignore the contents of each case switch, I am just trying to get it to throw the print command at least so I know that the switch is operating.
# I have tried: case "1", case 1, case [1], case '1', and other variations.
#!/usr/bin/perl
# script name: phonebook.pl
while ( 1 ) {
print "Welcome to the Registry Searcher!\n";
print "Please enter a command matching one of the options below.\n";
print "1) List records alphabetically\n";
print "2) List records reverse alphabetically\n";
print "3) Search the Registry by Name\n";
print "4) Search the Registry by Birthday\n";
print "5) Exit\n";
print "Choice: ";
$in = <>;
# user enters "1".
use Switch;
switch ($in) {
case 1 {
print "Please choose either first or last name (f/l): ";
$type = <>;
if ( $type == f ) {
sort list.txt;
} elsif ( $type == "l" ) {
sort -k2 list.txt;
} else {
print "Choice not recognized.\n";
}
print "Please press enter to continue...";
$cont = <>;
}
case 2 {
print "Please choose either first or last name (f/l): ";
$type = <>;
if ( $type == "f" ) {
sort -r list.txt
} elsif ( $type == "l" ) {
sort -rk2 list.txt
} else {
print "Choice not recognized.\n";
}
print "Please press enter to continue...";
$cont = <>;
}
case 3 {
print "Please enter a last name to search for: ";
$name = <>;
# awk '/^[A-Z][a-z]+ '$name'/{print}' list.txt;
print "Please press enter to continue...";
$cont = <>;
}
else {
print "not found\n";
}
}
}
You must add chomp($in); before the switch to remove the newline.
<> read a line in a stream and include the linefeed, the behaviour is different from a scanf-like function.
You really don't want to be using Switch; that's a very old source-filter module that's unreliable, and in any case was removed from Perl a long time ago.
For better modern alternatives see Switch::Plain, or the given/when built-in operators, though beware of the latter's behaviour with "experimental" warnings.

Why is my output printing out twice?

I've written a script that takes a command line argument -s, and allows me to add a search term after it.
It then feeds that into my first function, connects to a SQL database, searches for the term, and returns the results in an array.
It then calls the second function, prints the array, and outputs a -1 or a 0 depending on whether any results were found.
Finally it is supposed to check if the result is not equal to 0, and if so print out that no results were found.
Everything is working but my results are printing twice. Any idea why?
#!/usr/bin/perl -w
use warnings;
use DBI;
use Getopt::Std;
use strict;
getopt('s:');
our ($opt_s);
my $search = $opt_s;
my #array = function1($search);
&function1($search);
&function2(#array);
if (&function2(#array) != 0) {
print "No results found for '$search'", "\n";
}
sub function1 {
my $search = $_[0];
our $dbh = DBI->connect("dbi:mysql:dbname=database", "root", "password")
or die $DBI::errstr;
my $sql = $dbh->selectall_arrayref(
"SELECT Player from Players_Sport where Sport like '$search'")
or die $DBI::errstr;
my #array = map { $_->[0] } #$sql;
$dbh->disconnect
or warn "Disconnection failed: $DBI::errstr\n";
return #array;
}
sub function2 {
my #array = #_;
my $arrayvalue;
print("\n", "#array", "\n");
if (scalar(#array) == 0) {
$arrayvalue = -1;
}
else {
$arrayvalue = 0;
}
return $arrayvalue;
}
You're calling &function2(#array); twice, which causes "\n", "#array", "\n" to be printed twice. Just call the function once, store the return value in a variable, and test the variable rather than calling the function again — or, even better, in this specific instance you could just dispense with the first call entirely.

Perl reprompt user input until get exact expected input

How to make script very interactive?
Script purpose:
Get user entered number and find mod via get_mod_val function; if it returns 1 then say "good" and exit the script; if it returns 0, then ask user to enter odd number.
Validation:
accept only numbers, no character, decimal, special characters.
do not end the script until user has entered the odd number.
The following code works fine; it follows above validation point 1 rules, but it is not very interactive as per our requirement law. When above rules fails, script exits and we need to execute the script again.
How can we make the following code very interactive?
$ip_no = $ARGV[0];
if ($ip_no!="") {
$get_mod_op = get_mod_val($ip_no);
if ($ip_no =~ /\D/){
print "Entered number only.....";
exit;
}else{
if ($get_mod_op==1) {
print "Good odd number(${get_mod_op}): ${ip_no} ";
exit;
}else{
print "Good even number(${get_mod_op}): ${ip_no} ";
exit;
}
}
}else{
print "Enter number.";
exit;
}
Thank to all, finally i have done the code with your suggestions, Following snippet reached my requirement, review the code and advise for optimization.
if ($ARGV[0] != "") {
$user_ip = $ARGV[0];
}elsif (($ARGV[0] =~ /\D/ ) || ($ARGV[0] eq "") ){
print "Enter odd number only: ";
$user_ip = <STDIN>;
}else{
$user_ip = <STDIN>;
}
do{ #get mod value, if mod_off is 1 then entered number is odd otherwise ask user to enter the odd number
$mod_off = find_mod_val($user_ip);
if (($user_ip == "") || ($user_ip eq "") || ($user_ip eq "") || ($mod_off == 0)) {
print "Enter odd number only: ";
$user_ip = <STDIN>;
}
}until($mod_off == 1);
print "Good odd number ${user_ip}";
sub find_mod_val($user_ip){
return $user_ip%2;
}
__END__
TIMTOWTDI, this time with recursion! The benefit is that you can reuse the function to validate other inputs too. I guess you could make a sub that wraps a loop, but c'mon this is fun!
sub get_input {
my ($message, $valid) = #_;
print "$message: ";
chomp(my $response = <>);
unless( $response =~ $valid ) {
print "Invalid response!\n";
$response = get_input($message, $valid);
}
return $response;
}
my $response = get_input("Enter a number", qr/^\d+$/);
print "Got: $response\n";
Don't use ARGV[0] and read from STDIN (so you need to run your script and type the word you want). I have rewritten your code:
my $finish = 0;
while( ! $finish ) {
print "Enter number.";
my $ip_no = <STDIN>;
chomp($ip_no);
$get_mod_op = get_mod_val($ip_no);
if( $ip_no =~ /\D/ ){
print "Entered number only.....";
}
elsif( $get_mod_op == 1 ) {
print "Good odd number(${get_mod_op}): ${ip_no} ";
$finish = 1;
}
else {
print "Good even number(${get_mod_op}): ${ip_no} ";
$finish = 1;
}
}
Are you sure you want it to be interactive; doing that makes it much less useful in other scripts.
If you must, then put the testing code into a sub, then use the sub to validate $ARGV[0], and if that fails, go into a loop that requests input and runs the validation.
Check out the Perl IO::Prompt module.
my $val;
for (;;) {
print "Some prompt: ";
$val = <STDIN>;
chomp $val;
last if is_valid($val);
print "Bad input. Valid inputs are ...\n";
}

just can't get perl working as expected ( conditionals and variable declaring )

EDIT:
I will try a better explication this time, this is the exact code from my script (sorry for all them coments, they are a result of your sugestions, and apear in the video below).
#use warnings;
#use Data::Dumper;
open(my $tmp_file, ">>", "/tmp/some_bad.log") or die "Can not open log file: $!\n";
#if( $id_client != "")
#allowed_locations = ();
#print $tmp_file "Before the if: ". Data::Dumper->Dump([\#allowed_locations, $id_client]) . "";
if( $id_client )
{
# print $tmp_file "Start the if: ". Data::Dumper->Dump([\#allowed_locations, $id_client]) . "";
# my $q = "select distinct id_location from locations inner join address using (id_db5_address) inner join zona_rural_detaliat using (id_city) where id_client=$id_client";
# my $st = &sql_special_transaction($sql_local_host, $sql_local_database, $sql_local_root, $sql_local_root_password, $q);
# print $tmp_file "Before the while loop: ref(st)='". ref($st) . "\n";
# while((my $id)=$st->fetchrow())
# {
# print $tmp_file "Row the while loop: ". Data::Dumper->Dump([$id]) . "";
# my $id = 12121212;
# push(#allowed_locations, $id);
# }
# print $tmp_file "After the while loop: ref(st)='". ref($st) . "\n";
# my($a) = 1;
#} else {
# my($a) = 0;
}
#print $tmp_file "After the if: ". Data::Dumper->Dump([\#allowed_locations, $id_client]) . "";
close($tmp_file) or die "Can not close file: $!\n";
#&html_error(#allowed_locations);
First off all, somebody said that I should try to run it in command line, the script works fine in command line (no warnings, It was uncommented then), but when triyng to load in via apache in the browser it fails, please see this video where I captured the script behavior, what I tried to show in the video:
I have opened 2 tabs the first doesn't define the variable $id_client, the second defines the variable $id_client that is read from GET: ?id_client=36124 => $id_client = 36124; , both of them include the library in the video "locallib.pl"
When running the script with all the
new code commented the page loads
when uncoment the line that defines
the #allowed_locations = (); the
script fails
leave this definition and uncoment
the if block, and the definition of
my $a; in the if block; Now the script works fine when $id_client is
defined, but fails when $id_client
is not defined
Uncoment the else block and the
definition of my $a; in the else
block. Now the script works fine
with or without $id_client
now comment all the my $a;
definisions and comment the else
block, the script fails
but if I'm using open() to open
a file before the IF, and
close() to close it after the if it does't fail even if the IF block
is empty and event if there is no
else block
I have replicated all the steps when running the script in the command line, and the script worked after each step.
I know it sounds like something that cannot be the behavior of the script, but please watch the video (2 minutes), maybe you will notice something that I'm doing wrong there.
Using perl version:
[root#db]# perl -v
This is perl, v5.8.6 built for i386-linux-thread-mult
Somebody asked if I don't have a test server, answer: NO, my company has a production server that has multiple purposes, not only the web interface, and I cannot risk to update the kernel or the perl version, and cannot risk instaling any debuger, as the company owners say: "If it works, leave it alone", and for them the solution with my ($a); is perfect beacause it works, I'm asking here just for me, to learn more about perl, and to understand what is going wrong and what can I do better next time.
Thank you.
P.S. hope this new approach will restore some of my -1 :)
EDIT:
I had success starting the error logging, and found this in the error log after each step that resulted in a failure I got this messages:
[Thu Jul 15 14:29:19 2010] [error] locallib.pl did not return a true value at /var/www/html/rdsdb4/cgi-bin/clients/quicksearch.cgi line 2.
[Thu Jul 15 14:29:19 2010] [error] Premature end of script headers: quicksearch.cgi
What I found is that this code is at the end of the main code in the locallib.pl after this there are sub definitions, and locallib.pl is a library not a program file, so it's last statement must returns true. , a simple 1; statement at the end of the library ensures that (I put it after sub definitions to ensure that noobody writes code in the main after the 1;) and the problem was fixed.
Don't know why in CLI it had no problem ...
Maybe I will get a lot of down votes now ( be gentle :) ) , but what can I do ...and I hope that some newbies will read this and learn something from my mistake.
Thank you all for your help.
You need to explicitly check for definedness.
If you want to enter the loop when $client is defined,
use if ( defined $client ).
If you want to enter the loop when $client is defined and a valid integer,
use if ( defined $client && $client =~ /^-?\d+$/ ).
I assume it's an integer from the context, if it can be a float, the regex needs to be enhanced - there's a standard Perl library containing pre-canned regexes, including ones to match floats. If you require a non-negative int, drop -? from regex's start.
If you want to enter the loop when $client is defined and a non-zero (and assuming it shouldn't ever be an empty string),
use if ( $client ).
If you want to enter the loop when $client is defined and a valid non-zero int,
use if ( $client && $client =~ /^-?\d+$/ ).
Your #ids is "undef" when if condition is false, which may break the code later on if it relies on #ids being an array. Since you didn't actually specify how the script breaks without an else, this is the most likely cause.
Please see if this version works (use whichever "if" condition from above you need, I picked the last one as it appears to match the closest witrh the original code's intent - only enter for non-zero integers):
UPDATED CODE WITH DEBUGGING
use Data::Dumper;
open(my $tmp_file, ">", "/tmp/some_bad.log") or die "Can not open log file: $!\n";
#ids = (); # Do this first so #ids is always an array, even for non-client!
print $tmp_file "Before the if: ". Data::Dumper->Dump([\#ids, $client]) . "\n";
if ( $client && $client =~ /^-?\d+$/ ) # First expression catches undef and zero
{
print $tmp_file "Start the if: ". Data::Dumper->Dump([\#ids, $client]) . "\n";
my $st = &sql_query("select id from table where client=$client");
print $tmp_file "Before the while loop: ref(st)='". ref($st) . "'\n";
while(my $row = $st->fetchrow())
{
print $tmp_file "Row the while loop: ". Data::Dumper->Dump([row]) . "'\n";
push(#ids, $row->[0]);
}
print $tmp_file "After the while loop: ref(st)='". ref($st) . "'\n";
# No need to undef since both variables are lexically in this block only
}
print $tmp_file "After the if\n";
close($tmp_file) or die "Can not close file: $!\n";
when checking against a string, == and != should be respectively 'eq' or 'ne'
if( $client != "" )
should be
if( $client ne "" )
Otherwise you don't get what you're expecting to get.
Always begin your script with :
use warnings;
use strict;
these will give you usefull informations.
Then you could write :
my #ids;
if (defined $client) {
#ids = (); # not necessary if you run this part only once
my $st = sql_query("select id from table where client=$client");
while( my ($id) = $st->fetchrow ) {
push #ids, $id;
}
} else {
warn '$client not defined';
}
if (#ids) { # Your query returned something
# do stuff with #ids
} else {
warn "client '$client' does not exist in database";
}
Note: this answer was deleted because I consider that this is not a real question. I am undeleting it to save other people repeating this.
Instead of
if( $client != "" )
try
if ($client)
Also, Perl debugging is easier if you
use warnings;
use strict;
What I found is that this code is at the end of the main code in the locallib.pl after this there are sub definitions, and locallib.pl is a library not a program file, so it's last statement must returns true, a simple 1; statement at the end of the library ensures that (put it after sub definitions to ensure that noobody writes code in the main after the 1;) and the problem was fixed.
The conclusion:
I have learned that every time you write a library or modify one, ensure that it's last statment returns true;
Oh my... Try this as an example instead...
# Move the logic into a subroutine
# Forward definition so perl knows func exists
sub getClientIds($);
# Call subroutine to find id's - defined later.
my #ids_from_database = &getClientIds("Joe Smith");
# If sub returned an empty list () then variable will be false.
# Otherwise, print each ID we found.
if (#ids_from_database) {
foreach my $i (#ids_from_database) {
print "Found ID $i \n";
}
} else {
print "Found nothing! \n";
}
# This is the end of the "main" code - now we define the logic.
# Here's the real work
sub getClientIds($) {
my $client = shift #_; # assign first parameter to var $client
my #ids = (); # what we will return
# ensure we weren't called with &getClientIds("") or something...
if (not $client) {
print "I really need you to give me a parameter...\n";
return #ids;
}
# I'm assuming the query is string based, so probably need to put it
# inside \"quotes\"
my $st = &sql_query("select id from table where client=\"$client\"");
# Did sql_query() fail?
if (not $st) {
print "Oops someone made a problem in the SQL...\n";
return #ids;
}
my #result;
# Returns a list, so putting it in a list and then pulling the first element
# in two steps instead of one.
while (#result = $st->fetchrow()) {
push #ids, $result[0];
}
# Always a good idea to clean up once you're done.
$st->finish();
return #ids;
}
To your specific questions:
If you want to test if $client is defined, you want "if ( eval { defined $client; } )", but that's almost certainly NOT what you're looking for! It's far easier to ensure $client has some definition early in the program (e.g. $client = "";). Also note Kaklon's answer about the difference between ne and !=
if (X) { stuff } else { } is not valid perl. You could do: if (X) { stuff } else { 1; } but that's kind of begging the question, because the real issue is the test of the variable, not an else clause.
Sorry, no clue on that - I think the problem's elsewhere.
I also echo Kinopiko in recommending you add "use strict;" at the start of your program. That means that any $variable #that %you use has to be pre-defined as "my $varable; my #that; my %you;" It may seem like more work, but it's less work than trying to deal with undefined versus defined variables in code. It's a good habit to get into.
Note that my variables only live within the squiggliez in which they are defined (there's implicit squiggliez around the whole file:
my $x = 1;
if ($x == 1)
{
my $x = 2;
print "$x \n"; # prints 2. This is NOT the same $x as was set to 1 above.
}
print "$x \n"; # prints 1, because the $x in the squiggliez is gone.