prompting multiple questions to user (yes/no & file name input) - perl

I want to ask the user multiple questions. I have two types of questions: Y/N or filename input. I'm not sure how to place this all into a nice if structure. And I'm not sure if I should use 'else' statements too. Could someone help we with this? This is what I have so far:
print "Do you want to import a list (Y/N)?"; # first question yes/no
my $input = <STDIN>;
chomp $input;
if ($input =~ m/^[Y]$/i){ #match Y or y
print "Give the name of the first list file:\n";
my $list1 = <STDIN>;
chomp $list1;
print "Do you want to import another gene list file (Y/N)?";
if ($input =~ m/^[Y]$/i){
print "Give the name of the second list file:\n" # can I use $input or do I need to define another variable?;
$list2 = <STDIN>;
chomp $list2;
print "Do you want to import another gene list file (Y/N)?";
}
}

One word: Abstraction.
The solution you currently chose does not scale well, and contains too much repeated code. We will write a subroutine prompt that hides much of the complexity from us:
sub prompt {
my ($query) = #_; # take a prompt string as argument
local $| = 1; # activate autoflush to immediately show the prompt
print $query;
chomp(my $answer = <STDIN>);
return $answer;
}
And now a promt_yn that asks for confirmation:
sub prompt_yn {
my ($query) = #_;
my $answer = prompt("$query (Y/N): ");
return lc($answer) eq 'y';
}
We can now write your code in a way that actually works:
if (prompt_yn("Do you want to import a list")){
my $list1 = prompt("Give the name of the first list file:\n");
if (prompt_yn("Do you want to import another gene list file")){
my $list2 = prompt("Give the name of the second list file:\n");
# if (prompt_yn("Do you want to import another gene list file")){
# ...
}
}
Oh, so it seems you actually want a while loop:
if (prompt_yn("Do you want to import a list")){
my #list = prompt("Give the name of the first list file:\n");
while (prompt_yn("Do you want to import another gene list file")){
push #list, prompt("Give the name of the next list file:\n");
}
...; # do something with #list
}
The #list is an array. We can append elements via push.

A while ago I end up with following:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
if (&prompt_yn("CONTINUE")){
my #res = split(" ",&prompt("ENTER INPUT")) ;
print Dumper #res;
}
else{
print "EXIT\n";
}
sub prompt_yn{
my ($query) = #_;
$query = $query . " (Y/N): ";
print "$query";
while (<>) {
$_ =~ s/^\s+|\s+$//g;
$_ =~ s/\r|\n//g;
if ($_ =~ /\S/){
if ($_ =~ /^y$|^yes$/i){
# if you want information message about entered value uncomment this
# print "You have entered Y\n";
return 1;
}
elsif ($_ =~ /^n$|^no$/i){
# if you want information message about entered value uncomment this
# print "You have entered N\n";
return 0;
}
else{
# if you want information message about entered value uncomment this
# print "You have entered wrong value try again: \n";
}
}
print "$query";
}
}
sub prompt{
my ($query) = #_;
$query = $query . ": ";
print "$query";
while (<>) {
$_ =~ s/^\s+|\s+$//g;
$_ =~ s/\r|\n//g;
if ($_ =~ /\S/){
return $_;
}
print "$query";
}
}
Compared to previous solutions this handles empty inputs.

You can use Sub Routines.
This helps you visibly and logically keep everything in-line.
for instance
&main();
sub main {
print "Do you want to import a list(Y/N)";
my $input = ;
chomp $input;
if($input =~ m/^[Y]$/i) {
&importfile();
} elsif ($input =~ m/^[N]$/i) {
print "you said no";
} else {
print "Invalid option";
}
}
sub importfile
{
print "file name please ";
my $file = STDIN;
# import and process the file here.....
&main();
}
So you can import at many files this way.

Related

can't loop through the whole thing to start at the beginning after it shows your results

I am really new in perl and I am writing this program that gives you the unique words that are in a text file. however I don't know how to make it loop to ask the user for another file or to quit the program altogether.
I tried to put my whole code under a do until loop and it did not work
use 5.18.0;
use warnings;
use strict;
print "Enter the name of the file: ";
my %count;
my $userinput = <>; #the name of the text file the user wants to read
chomp($userinput); #take out the new line comand
my $linenumb = $ARGV[1];
my $uniqcount = 0;
#opens the file if is readeable
open(FH, '<:encoding(UTF-8)', $userinput) or die "Could not open file '$userinput' $!";
print "Summary of file '$userinput': \n";
my ($lines, $wordnumber, $total) = (0, 0, 0);
my #words = ();
my $count =1;
while (my $line = <FH>) {
$lines++;
my #words = split (" ", $line);
$wordnumber = #words;
print "\n Line $lines : $wordnumber ";
$total = $total+$wordnumber;
$wordnumber++;
}
print "\nTotal no. of words in file are $total \n";
#my #uniq = uniq #words;
#print "Unique Names: " .scalar #uniq . "\n";
close(FH);
It's often a good idea to put complicated pieces of your code into subroutines so that you can forget (temporarily) how the details work and concentrate on the bigger picture.
I'd suggest that you have two obvious subroutines here that might be called get_user_input() and process_file(). Putting the code into subroutines might look like this:
sub get_user_input {
print "Enter the name of the file: ";
my $userinput = <>; #the name of the text file the user wants to read
chomp($userinput); #take out the new line comand
return $userinput;
}
sub process_file {
my ($file) = #_;
#opens the file if is readeable
# Note: Changed to using a lexical filehandle.
# This will automatically be closed when the
# lexical variable goes out of scope (i.e. at
# the end of this subroutine).
open(my $fh, '<:encoding(UTF-8)', $file)
or die "Could not open file '$file' $!";
print "Summary of file '$file': \n";
# Removed $lines variable. We'll use the built-in
# variable $. instead.
# Moved declaration of $wordnumber inside the loop.
# Removed #words and $count variables that aren't used.
my $total = 0;
# Removed $line variable. We'll use $_ instead.
while (<$fh>) {
# With no arguments, split() defaults to
# behaving as split ' ', $_.
# When assigned to a scalar, split() returns
# the number of elements in the split list
# (which is what we want here - we never actually
# use the list of words).
my $wordnumber = split;
print "\n Line $. : $wordnumber ";
# $x += $y is a shortcut for $x = $x + $y.
$total += $wordnumber;
$wordnumber++;
}
print "\nTotal no. of words in file are $total \n";
}
And then you can plug them together with code something like this:
# Get the first filename from the user
my $filename = get_user_input();
# While the user hasn't typed 'q' to quit
while ($filename ne 'q') {
# Process the file
process_file($filename);
# Get another filename from the user
$filename = get_user_input();
}
Update: I've cleaned up the process_file() subroutine a bit and added comments about the changes I've made.
Wrap everything in a neverending loop and conditionally jump out of it.
while () {
my $prompt = …
last if $prompt eq 'quit';
… # file handling goes here
}

How Can I avoid a Indefinite Loop While Searching a string from a no of files

In my below program, I was trying to search a string from no of files In a folder but output Is printing in continuous manner rather than stopping after required search. Can some one pls help to point out the error ?
i.e. I am trying to Search the string "VoLTE SIPTX: [SIPTX-SIP] ==> REGISTER" from #files but I am not getting the desired output but I am getting repetitive output of my strings.
# #!/usr/bin/perl
# use strict;
use warnings;
&IMS_Compare_Message();
sub IMS_Compare_Message
{
print "Entering the value i.e. the IMS Message to compare with";
my $value = '';
my $choice = '';
my $loop = '';
print "\nThe script path & name is $0\n";
print "\nPlease enter desired number to select any of the following
(1) Start Comparing REGISTER message !!
(2) Start Comparing SUBSCRIBE message
(3) Start Comparing INVITE message \n";
$value = <STDIN>;
if ($value == 1 )
{
print "\n Start Comparing REGISTER message\n\n";
$IMS_Message = "VoLTE SIPTX: [SIPTX-SIP] ==> REGISTER";
#chomp ($IMS_Message);
}
elsif ($value == 2)
{
print "\n SUBSCRIBE message Flow\n\n";
}
elsif ($value == 3)
{
print "\n INVITE message Flow\n\n";
}
else
{
print "\nThe input is not valid!\n";
print "\nDo you want to continue selecting a Automation Mode again (Y or N)?\n";
$choice = <STDIN>;
if( $choice =~ /[Yy]/) {
test_loop();
} else {
exit;
}
}
my $kw = "$IMS_Message";
my #files = grep {-f} (<*main_log>);
foreach my $file (#files)
{
open(my $fh, '<', $file) or die $!;
my #content = <$fh>;
close($fh);
my $l = 0;
$search = chomp ($kw);
#my $search = quotemeta($kw);
foreach (#content)
{ # go through every line for this keyword
$l++;
if (/$search/)
{
printf 'Found keyword %s in file %s, line %d:%s'.$/, $kw, $file, $l, $_
}
}
}
}
After Modificaiton
# #!/usr/bin/perl
use strict;
use warnings;
print "Entering the value i.e. the IMS Message to compare with";
my $value = '';
my $choice = '';
my $loop = '';
my $IMS_Message = '';
my $search = '';
my $kw = '';
print "\nThe script path & name is $0\n";
print "\nPlease enter desired number to select any of the following
(1) Start Comparing REGISTER message !!
(2) Start Comparing SUBSCRIBE message
(3) Start Comparing INVITE message \n";
$value = <STDIN>;
if ($value == 1 )
{
print "\n Start Comparing REGISTER message\n\n";
$IMS_Message = "VoLTE SIPTX: [SIPTX-SIP] ==> REGISTER";
#chomp ($IMS_Message);
}
elsif ($value == 2)
{
print "\n SUBSCRIBE message Flow\n\n";
}
elsif ($value == 3)
{
print "\n INVITE message Flow\n\n";
}
else
{
print "\nThe input is not valid!\n";
print "\nDo you want to continue selecting a Automation Mode again (Y or N)?\n";
$choice = <STDIN>;
if( $choice eq /[Yy]/) {
test_loop();
} else {
exit;
}
$kw = $IMS_Message;
$search = qr/\Q$kw/;
for my $file ( grep { -f } glob '*main_log' ) {
open my $fh, '<', $file or die qq{Unable to open "$file" for input: $!};
while ( <$fh> ) {
if ( /$search/ ) {
printf "Found keyword %s in file %s, line %d: %s\n", $kw, $file, $., $_;
last;
}
}
}
}
Here are some observations on your code
Your approach to debugging appears to be to try things at random to see if they work. It would be far more fruitful to add diagnostic print statements so that you can compare variables' actual values with what you expect
Error and warning messages are useful information, and it is foolish to comment out use strict to make them go away
Don't call subroutines with an ampersand &. That hasn't been best practice for twenty years now
Lay your code out tidily and cinsistently, so that both you and any people you ask for help can read it easily. As it stands it is impossible to tell where blocks start and end without counting brace characters {...}
Variables should be declared with my as close as possible to their first point of use, and not all at once at the top of the file or subroutine
chomp is necessary only for strings that have been read from the terminal or from a file. It returns the number of characters removed, not the trimmed string
if( $choice =~ /[Yy]/ ) { ... } will check only whether the string contains a Y, so if the operator enters MARRY ME! it will return true. You should use string equality eq to check whether a single Y character has been typed
You shouldn't put scalar variables alone inside double quotes. At best it will make no difference, and just add noise to your code; at worst it will completely change the value of the variable. Just my $kw = $IMS_Message is correct
Unless you require non-sequential access to the contents of a file, it is best to use a while loop to read and process it line by line, rather than read the whole thing into an array and process each element of the array. This also allows you to use the built-in line number variable $. instead of implementing your own $l
The main problem is that you have derived $search from the result of chomp $kw, which sets $search to the number of characters removed by chomp. This is always zero because $kw is a copy of $IMS_Message, which has no newline at the end. That means you are checking all the lines of every file for the character 0, and not for the message that you intended. The correct way is my $search = quotemeta($kw) which you had in place but have commented out, presumably as a result of your policy of "debugging by guesswork"
Fixing these things, your code should look something like this
my $search = qr/\Q$kw/;
for my $file ( grep { -f } glob '*main_log' ) {
open my $fh, '<', $file or die qq{Unable to open "$file" for input: $!};
while ( <$fh> ) {
if ( /$search/ ) {
printf "Found keyword %s in file %s, line %d: %s\n", $kw, $file, $., $_;
last;
}
}
}

Can't find a string in array

I have a file with almost 1,500 names of Marvel heroes, each name in new line. I have to ask user what his favourite hero is and find out if it's a hero from the list or not. Here's what I have right now. It doesn't work: I can guess only the last hero from the list. For the rest it just prints that they are not on the list.
print "Whats your favourite hero?\n";
my $hero = <stdin>;
chomp $hero;
open FILE, "<list_marvel.txt";
my #marvel = <FILE>;
chomp(#marvel);
my $result = 0;
foreach (#marvel) {
if ($_ eq $hero);
}
if ($result == 1) {
print "That hero is on the list";
}
else {
print "$hero is not on the list.\n";
}
Here are two files:
-Perl code : Perl Code
-List of heroes : List
Your program has a syntax error and won't compile. It certainly won't find only the last name on the list
The main problem is that you never set $result, and if($_ eq $hero) should be something like $result = 1 if($_ eq $hero)
You must always use strict and use warnings at the top of every Perl program you write. It is an enormous help in finding straighforward problems
Here's a working version
use strict;
use warnings;
my $filename = 'list_marvel.txt';
open my $fh, '<', $filename or die qq{Unable to open "'list_marvel.txt'": $!};
print "Whats your favourite hero? ";
my $hero = <>;
chomp $hero;
my $found;
while ( <$fh> ) {
chomp;
if ( $_ eq $hero ) {
++$found;
last;
}
}
print $found ? "$hero is on the list\n" : "$hero is not on the list";
You don't set $result anywhere to true.
Make your foreach loop like this:
foreach(#marvel){
$result = $_ eq $hero;
}
or
foreach (#marvel){
$result = 1 if $_ eq $hero
}
You forgot to increment your $result. If you indent your code properly, it is easier to see.
foreach (#marvel) {
# here something is missing
if ( $_ eq $hero );
}
Add $result++ if $_ eq $hero; in the foreach.
You should always use strict and use warnings. That would have told you about a syntax error near );.
Also consider using the three argument open with lexical filehandles.
Rewritten it looks like this:
use strict;
use warnings;
use feature 'say'; # gives you say, which is print with a newline at the end
say "What's you favourite hero?";
my $hero = <STDIN>;
chomp $hero;
# alsways name variables so it's clear what they are for
my $found = 0;
# die with the reason of error if something goes wrong
open my $fh, '<', 'list_marvel.txt' or die $!;
# read the file line by line
while ( my $line = <$fh> ) {
chomp $line;
if ( $line eq $hero ) {
# increment so we know we 'found' the hero in the list
$found++;
# stop reading at the first hit
last;
}
}
close $fh;
# no need to check for 1, truth is enough
if ( $result ) {
say "That hero is on the list.";
}
else {
say "$hero is not on the list.";
}
First, you miss setting the $result at around if($_ eq $hero).
Then, you may wish to make you comparison case insensitive. This would require a regular expression, e.g.:
$result = 1 if (/^$hero$/i);
Just modified your code. After if condition increment $result. Always use use strict and use warnings and always use 3 arguments to open a file.
use strict;
use warnings;
print "Whats your favourite hero?\n";
my $hero = <stdin>;
chomp $hero;
open FILE, "<", "list_marvel.txt" or die $!;
chomp (my #marvel = <FILE>);
close FILE;
my $result = 0;
foreach my $name (#marvel)
{
if($name eq $hero)
{
$result++;
}
}
if ($result == 1)
{
print "That hero is in the list.\n";
}
else
{
print "$hero is not in the list.\n";
}
This will take a single user entry from STDIN. It will run through the file of hero names, and if one matches the user entry it will print the name and exit the loop. If the name is not found it will tell you:
use warnings;
use strict;
open my $file1, '<', 'input.txt' or die $!;
print "Enter hero: ";
chomp(my $hero = <STDIN>);
my $result = 0;
while(<$file1>){
chomp;
if (/$hero/){
print "$_\n";
$result++;
last;
}
}
print "hero not in list\n" if $result == 0;

cant retrieve values from hash reversal (Perl)

I've initialized a hash with Names and their class ranking as follows
a=>5,b=>2,c=>1,d=>3,e=>5
I've this code so far
my %Ranks = reverse %Class; #As I need to find out who's ranked first
print "\nFirst place goes to.... ", $Ranks{1};
The code only prints out
"First place goes to...."
I want it to print out
First place goes to....c
Could you tell me where' I'm going wrong here?
The class hash prints correctly
but If I try to print the reversed hash using
foreach $t (keys %Ranks) {
print "\n $t $Ranks{$t}"; }
It prints
5
abc23
cab2
ord
If this helps in any way
FULL CODE
#Script to read from the data file and initialize it into a hash
my %Code;
my %Ranks;
#Check whether the file exists
open(fh, "Task1.txt") or die "The File Does Not Exist!\n", $!;
while (my $line = <fh>) {
chomp $line;
my #fields = split /,/, $line;
$Code{$fields[0]} = $fields[1];
$Class{$fields[0]} = $fields[2];
}
close(fh);
#Prints the dataset
print "Code \t Name\n";
foreach $code ( keys %Code) {
print "$code \t $Code{$code}\n";
}
#Find out who comes first
my %Ranks = reverse %Class;
foreach $t (keys %Ranks)
{
print "\n $t $Ranks{$t}";
}
print "\nFirst place goes to.... ", $Ranks{1}, "\n";
When you want to check what your data structures actually contain, use Data::Dumper. use Data::Dumper; local $Data::Dumper::Useqq = 1; print(Dumper(\%Class));. You'll find un-chomped newlines.
You need to use chomp. At present your $fields[2] value has a trailing newline.
Change your file read loop to this
while (my $line = <fh>) {
chomp $line;
my #fields = split /,/, $line;
$Code{$fields[0]} = $fields[1];
$Class{$fields[0]} = $fields[2];
}

I made a heads/tails script in perl and I want it to run more than once

i know there is a simple one-liner or command that will let it run over and over until i kill it, can someone show me?
#!/usr/bin/perl
print "Content-type: text/html\n\n";
print "Please type in either heads or tails: ";
$answer = <STDIN>;
chomp $answer;
while ( $answer ne "heads" and $answer ne "tails" ) {
print "I asked you to type heads or tails. Please do so: ";
$answer = <STDIN>;
chomp $answer;
}
print "Thanks. You chose $answer.\n";
print "Hit enter key to continue: ";
$_ = <STDIN>;
if ( $answer eq "heads" ) {
print "HEADS! you WON!\n";
} else {
print "TAILS?! you lost. Try again!\n";
}
Is the code. I want it to ask again and again after the initial run
Just wrap the main portion of the code in a while loop.
#!/usr/bin/perl
print "Content-type: text/html\n\n";
while (1) {
print "Please type in either heads or tails: ";
$answer = <STDIN>;
chomp $answer;
while ( $answer ne "heads" and $answer ne "tails" ) {
print "I asked you to type heads or tails. Please do so: ";
$answer = <STDIN>;
chomp $answer;
}
print "Thanks. You chose $answer.\n";
print "Hit enter key to continue: ";
$_ = <STDIN>;
if ( $answer eq "heads" ) {
print "HEADS! you WON!\n";
} else {
print "TAILS?! you lost. Try again!\n";
}
}
a lot of assumptions here, but a "one-liner or command" from a bash shell can be done with:
$ while true; do perl yourscript.pl; done
kbenson is correct that you can surround your code in an infinite loop. A slightly more elegant way of doing this is to make a function which plays one round, then make an infinite loop around that function call. I use a few more tricks here, some of which may be new to you, if you don't understand something, please ask. Also I agree with cjm, I'm not sure why the content type is there so I left it out.
#!/usr/bin/env perl
use strict;
use warnings;
while (1) {
play_round();
print "Would you like to play again?: ";
my $answer = <STDIN>;
if ($answer =~ /no/i) {
print "Thanks for playing!\n";
last; #last ends the loop, since thats the last thing exit would work too
}
}
sub play_round {
print "Please type in either heads or tails: ";
my $answer = <STDIN>;
chomp $answer;
while ( $answer ne "heads" and $answer ne "tails" ) {
print "I asked you to type heads or tails. Please do so: ";
$answer = <STDIN>;
chomp $answer;
}
print "Thanks. You chose $answer. Now I'll flip.\n";
sleep 1;
my #coin = ('heads', 'tails');
my $side = $coin[int rand(2)];
print "And its ... $side! ";
if ( $answer eq $side ) {
print "You WON!\n";
} else {
print "Sorry, you lost. Try again!\n";
}
}