When I type after Enter a string: for example a, b, c and then two times Ctrl+D I get an endless loop which doesn't halt on ReadKey and which I can not stop with the Q key?
#!/usr/bin/env perl
use warnings;
use 5.10.1;
use Term::ReadKey;
while( 1 ) {
my $c;
say "Press the \"q\" key to quit.";
print "Press the \"e\" key to enter a string: ";
{
$|++;
Term::ReadKey::ReadMode 'ultra-raw';
$c = ReadKey 0;
Term::ReadKey::ReadMode 'restore';
}
exit if ord( $c ) == 3; # Control C
last if $c eq 'q';
if ( $c eq 'e' ) {
print "\nEnter a string: ";
my $string = <>;
if ( not defined $string ) {
say "Undefined";
}
else {
chomp $string;
say "You entered |$string|";
}
}
say "Still running";
}
After you type the two Ctrl-D (EOT), your programm will only receive NUL-bytes. And an infinity of them. Unfortunately you have an unconditional read in an infinite loop. Either you change that (e.g. give the user a lesson if he types something other than q or e and exit if he didn't get it after the third try), or you implement control characters correctly. Your module strips all control characters from the input before you even get it, but it provides the neccessary hooks. I urge you to add Ctrl-C as well (it only works when a line is expected, not when a char is being read).
Also, why not compare the input characters with string equality? $c eq "q" reads nicer.
The only line that will terminate your loop is this one:
last if ord( $c ) == 113;
So, the only way to escape the loop is to enter 'q' when the prompt asks you to enter 'e'.
Presumably you want an appropriately placed last inside your if statement, so that ctrl-d and/or any text will terminate.
Related
I am trying to create a simple perl assignment for equality check and goto label concept.
User enters numbers , equality check happens, user is asked if they want to check more , if yes then it repeats, else it exits. Using "goto " for this
Issue- y/n checks for repeating,y is for repeating Label. even if I enter "n" it keeps going to the label Loop .
Why is the "if" condition containing "goto" not getting honored?
Code below
#Checking Equality
Loop: print "\Checking Equality\n";
print "Enter number for variable a\n";
$a = <stdin>;
print "Enter number for variable b\n";
$b = <stdin>;
if ( $a == $b ) {
print 'a and b are equal';
print "\n\n";
}
else {
print 'a and b are not equal';
}
print "\n\n";
print "do you want to check more? Enter y/n\n";
$c = <stdin>;
if ( $c == "y" ) {
goto Loop;
}
elsif ( $c == "n" ) {
print "Exiting\n";
}
Output-
Checking Equality
Enter number for variable a
3
Enter number for variable b
4
a and b are not equal
do you want to check more? Enter y/n
n
Checking Equality #### despite of entering n goto Loop is getting executed
Enter number for variable a
Just use chomp function to remove newline(s), and use eq comparison for string.
use strict;
use warnings;
Loop: print "\nChecking Equality\n";
print "Enter number for variable a\n";
my $a = <stdin>;
print "Enter number for variable b\n";
my $b = <stdin>;
if ( $a == $b ) {
print 'a and b are equal';
print "\n\n";
}
else {
print 'a and b are not equal';
}
print "\n\n";
print "do you want to check more? Enter y/n\n";
chomp(my $c = <stdin>);
if ( $c eq "y" ) {
goto Loop;
}
else {
print "Exiting\n";
}
You have chosen not best approach to use a label for goto.
Instead loop would be more appropriate to perform user input. User stays in the loop until he/she will not specify that he/she ready to leave.
User's input also should be stripped off newline symbol \n before it can be used for comparison.
As user suppose to provide input several time then it would be beneficial to use small subroutine to print 'input prompt', collect input, strip newline symbol and return input value. By doing so the program becomes shorter and easier to read.
String comparison operation performed with eq, number comparison performed with ==.
use strict;
use warnings;
use feature 'say';
my $answer = 'y';
while( $answer eq 'y' ) {
my $num_1 = get_input('Enter variable num_1: ');
my $num_2 = get_input('Enter variable num_2: ');
my $compare = $num_1 == $num_2 ? 'EQUIAL' : 'NOT EQUIAL';
say "\nNumbers are $compare\n";
$answer = get_input('Would you like to continue? (y/n) ');
$answer = lc $answer;
say '-' x 35;
}
sub get_input {
my $msg = shift;
my $input;
print $msg;
$input = <>;
chomp $input;
return $input;
}
How do I accept a list of integers as input? The only thing I can think of is getting each integer from the list specifically using STDIN. Is there a better way to do this?
You want input a list of integers? I take it you mean that you want to enter a list of numbers, and accept that input if they're all integers.
In this program, I loop forever until I get a valid list, thus for (;;). Some people prefer while (1).
I use Scalar::Util's looks_like_number to test whether the input is numeric, and then use int to verify that the number is an integer. You could use a regular expression like /^\d+$/, but there's no guarantee that it works in all circumstances. Using int and looks_like_number guarantees the results.
I assume that a list of integers could be space separated or comma separated or both, thus my split /[\s,]+/.
You said:
The only thing I can think of is getting each integer from the list specifically using STDIN. Is there a better way to do this?
You read in data from a file handle, whether a file or something like STDIN. No way around that. However, you can at least make it a single input rather one at a time which I assume you mean.
By the way, I could have combined my numeric test with:
if( not looks_like_number $integer or $integer != $integer ) {
Since this is an or statement, if this would first check if $input looks numeric, and if it isn't, would warn about the input before checking to see if it's an integer. However, I'm not sure this is actually clearer than making it too separate statements.
#! /usr/bin/env perl
#
use strict;
use warnings;
use feature qw(say);
use Scalar::Util qw(looks_like_number);
my #integers; # Storage for my integers
#
# Keep looping until you have a valid input
#
INPUT:
for (;;) {
print "List of integers: ";
my $input = <STDIN>;
chomp $input;
#integers = split /[\s,]+/, $input;
#
# Verify inputted "number" is numeric and an integer
#
for my $integer ( #integers ) {
if( not looks_like_number $integer ) {
warn qq(Invalid number entered: "$integer");
next INPUT;
}
if( $integer != int $integer ) {
warn qq(Invalid integer entered: "$integer");
next INPUT;
}
}
#
# Exit if there's at least one integer in #integers
#
last if #integers;
}
say "Integers: " . join ": ", #integers;
This is how I did it:
$input = 0;
while($input != -1)
{
print "add input, finish with -1", "\n";
$input = <STDIN>;
chomp($input);
push(#array, $input);
}
#You also need to remove the last input, -1, with pop:
pop(#array);
print #array;
Console output:
add input, finish with -1
1
add input, finish with -1
2
add input, finish with -1
-1
12
If the user inputs a tab delimited string of numbers directly,
you can use the splice function to separate the strings.
#array = splice(/\t/,$array[0])
Here's one approach, taking a comma-separated list of integers:
my $input = <STDIN>;
chomp($input);
if ($input !~ m/^(\d+(,\d+)*)?$/) { die('invalid input'); }
my #input = split(/,/, $input );
Or you could read one integer per line:
my #input;
while (my $input = <STDIN>) {
chomp($input);
if ($input !~ m/^\d+$/) { die('invalid input'); }
push(#input, $input );
} ## end while
I am trying to write in Perl to count the number of each A/C/G/T bases in a DNA sequence. But couldn't figure out what went wrong in my code. "ATCTAGCTAGCTAGCTA" is the kind of data I am given.
#!usr/bin/perl
use strict;
use warnings;
my $in_file = <$ARGV[0]>;
open( my $FH_IN, "<", $in_file );
my $dna = <$FH_IN>;
my $index = 0;
my ( $freq_a, $freq_c, $freq_g, $freq_t ) = 0;
my $dna_length = length($dna);
while ( $index < $dna_length ) {
my $base = substr( $dna, $index, 1 );
if ( $base eq "A" ) {
$freq_a++;
$index++;
next;
} elsif ( $base eq "C" ) {
$freq_c++;
$index++;
next;
} elsif ( $base eq "G" ) {
$freq_g++;
$index++;
next;
} elsif ( $base eq "T" ) {
$freq_t++;
$index++;
next;
} else {
next;
}
}
print "$freq_a\n$freq_c\n$freq_g\n$freq_t\n";
exit;
I know there are a lot of ways to do it, but what I want to know is what I did wrong so I can learn from mistakes.
Perl has a special file handle to use with these kinds of problems: The diamond operator <>. It will read input from either a file name, if provided, and standard input if not.
Secondly, since you are only interested in ACGT, might as well look for only them, using a regex: /([ACGT])/g.
Thirdly, using a hash is the idiomatic way to count characters in Perl: $count{A}++
So your script becomes:
use strict;
use warnings;
my %count;
while (<>) {
while (/([ACGT])/g) {
$count{$1}++;
}
}
print "$_\n" for #count{qw(A C G T)};
Usage:
script.pl input.txt
Okay, you've done well so far and there's only one problem that stops your program from working.
It's far from obvious, but each line that's read from the file has a newline character "\n" at the end. So what's happening is that $index reaches the newline in the string, which is processed by the else clause (because it's not A, C, G or T) which just does a next, so the same character is processed over and over again. Your program just hangs, right?
You could remove the newline with chomp, but a proper fix is to increment $index in the else clause just as you do with all the other characters. So it looks like
else {
++$index;
next;
}
As you've suspected, there are much better ways to write this. There are also a couple of other nasties in your code, but that change should get you on your way for now.
It would be instructive for you to print out the values in $dna_length, $index and $base each time you go round the loop - immediately after you assign a value to $base.
Your code would be more robust if you moved the incrementing of $index to the end of the loop (outside of the if/elsif/else block) and removed all of your next statements.
An alternative "quick fix" is to chomp() the input line before you start processing it.
#!/usr/bin/env perl
use Term::ReadKey;
ReadMode 4;
END {
ReadMode 0; # Reset tty mode before exiting
}
while (<>) {
$key = ReadKey(0);
$key == "\x04" and last; # Ctrl+D breaks the loop
print $key;
}
When I had it without the while loop, it was printing back what I typed in.
It doesn't even produce any output at the end (if it was buffering it or something). Like I'd run it and type a few letters and hit Ctrl+D. It prints nothing.
I'm trying to make a program to convert mouse scroll escape codes into keypresses. I hope I'm not barking up the wrong tree.
This line
while (<>)
reads a line from STDIN (assuming you ran the program with no command line arguments). Once a line has been read, it enters the body of the while loop. Whatever you typed up to and including the newline is now in $_.
Now, you press a key, it's stored in $key and numerically compared to CTRL-D. Since neither is numeric, they both end up being zero, the loop terminates.
This is why you should turn on warnings which would have told you:
Argument "^D" isn't numeric in numeric eq (==) at ./tt.pl line 15, line 1.
Argument "c" isn't numeric in numeric eq (==) at ./tt.pl line 15, line 1.
Of course, it would make sense to put the loop-termination condition where it belongs as well:
#!/usr/bin/env perl
use strict;
use warnings;
use Term::ReadKey;
ReadMode 4;
END {
ReadMode 0; # Reset tty mode before exiting
}
my $input;
{
local $| = 1;
while ((my $key = ReadKey(0)) ne "\x04") {
print $key;
$input .= $key;
}
}
print "'$input'\n";
Just replace the while condition to:
while(1) {
# ...
}
I'm very new to Perl and have been given an assignment that is a simple guessing game where the user is given 8 chances to guess a number between 1 and 100. I keep getting the error mentioned above and cannot figure it out.
Here's my code:
use Modern::Perl;
my ($guess,$target,$counter);
$target = (int rand 100) + 1;
while ($guess < $target)
{
chomp ($guess=<>);
print "Enter guess $counter: ";
$counter++;
if ($guess eq $target) {
print "\nCongratulations! You guessed the secret number $target in $counter";
}
elsif ($guess > $target) {
print "\nYour guess, $guess, is too high.";
}
elsif ($guess < $target) {
print "\nYour guess, $guess, is too low.";
}
else {
print "You lose. The number was $target.";
}
}
Your code suffers a few issues. Here is my code, using a different approach:
#!/usr/bin/perl
use 5.012; # use strict; use feature 'say';
use warnings;
my $number = (int rand 100) + 1;
my $max_guesses = 8;
GUESS: foreach my $guess_no (1..$max_guesses) {
say "($guess_no) Please enter a guess:";
my $guess = <>;
chomp $guess;
unless ($guess =~ /^\d+$/) {
say "Hey, that didn't look like a number!";
redo GUESS;
}
if ($guess == $number) {
say "Congrats, you were on target!";
last GUESS;
} elsif ($guess < $number) {
say "Nay, your guess was TOO SMALL.";
} elsif ($guess > $number) {
say "Nay, your guess was TOO BIG.";
} else {
die "Illegal state";
}
if ($guess_no == $max_guesses) {
say "However, you have wasted all your guesses. YOU LOOSE.";
last GUESS;
}
}
Example usage:
$ perl guess-the-number.pl
(1) Please enter a guess:
15
Nay, your guess was TOO SMALL.
(2) Please enter a guess:
60
Nay, your guess was TOO BIG.
(3) Please enter a guess:
45
Nay, your guess was TOO BIG.
(4) Please enter a guess:
30
Nay, your guess was TOO SMALL.
(5) Please enter a guess:
38
Congrats, you were on target!
(all other corner cases (too many guesses, non-numbers as input) work as expected)
What did I do differently?
I didn't loop while the guess was too small (← bug!). Instead, I did a loop iteration for each guess. However, a while (1) loop would have worked as well.
I did a sanity check on the input, using a simple regular expression. It asserts that the input will be considered numeric by Perl. Else, you get to redo the guess.
I initialize all variables as soon as I declare them. This removes any possibilities of uninitialized values popping up in error messages.
I use proper comparision operators. Perl scalars come in two flavours: stringy and numeric:
Stringy Numeric
lt <
le <=
eq ==
ne !=
ge >=
gt >
cmp <=>
The say function prints the string like print, but appends a newline. This removes akward \ns at the beginning or the end of the string. It makes reading code easier.
You didn't mention which line was line #25! That could be important.
However, look at your while statement. Notice you're comparing $guess with $target, but you haven't set $guess yet. That's set inside you're while loop. This is why you're getting the undefined variable.
I also don't see where $counter is set. That could also be an issue.
Let's look at your while loop a bit closer:
while ( $guess < $target ) {
blah, blah, blah
}
Assuming that there's a $guess, you're looping until the user guesses a number bigger than the $target. Is that what you want? No, you stated that you wanted to give the user eight turns, and that's it. What you want is something more along the lines of:
my $count = 1;
while ( $count <= 8 ) {
$count++;
blah, blah, blah
}
A slightly nicer way of doing this is with a for loop:
for ( my $count = 1; $count <= 8; $count++ ) {
blah, blah, blah
}
However, these C-style for loops are so passé. Plus, there's a clearer way of doing this using the 1..8 syntax. This is the same as saying (1, 2, 3, 4, 5, 6, 7, 8). It's clean and easy to understand:
for my $counter (1..8) {
blah, blah, blah
}
(NOTE: Some people use a foreach keyword instead of a for keyword when doing this type of loop to distinguish it from the C-Style for loop. However, some people (cough! Damian Conway, cough!) frown upon the use of foreach since it doesn't really add clarity.)
Let's look at another aspect of your logic:
if ($guess eq $target) {
print "\nCongratulations! You guessed the secret number $target in $counter";
}
elsif ($guess > $target) {
print "\nYour guess, $guess, is too high.";
}
elsif ($guess < $target) {
print "\nYour guess, $guess, is too low.";
}
else { #Do this if the guess isn't equal to, greater than or less than the target
print "You lose. The number was $target.";
}
When will your else clause be executed? Answer is never. After all, you won't exeucte it unless $guess is neither greater than, equal to, or less than $target. Also, even if you did lose, you're still in that loop.
What you probably want to do is to put the You lose line outside the loop. That way, after the eighth guess and your loop ends, you get the You lose spiel.
Also, let's look at this line:
chomp ( $guess = <> );
print "Enter guess $counter: ";
First, the <> is the null filehandle operator which is very, very different from <STDIN>:
The null filehandle <> is special: it can be used to emulate the behavior of sed and awk, and any other Unix filter program that takes a list of filenames, doing the same to each line of input from all of them. Input from <> comes either from standard input, or from each file listed on the command line. Here's how it works: the first time <> is evaluated, the #ARGV array is checked, and if it is empty, $ARGV[0] is set to "-", which when opened gives you standard input. The #ARGV array is then processed as a list of filenames.
What you want is <STDIN>.
Also notice that you are getting the input before you print out your prompt. You really want to do this instead:
print "Enter guess $counter: ";
chomp ($guess = <STDIN>);
Also, you want to set $| to a nonzero value to turn off buffering. Otherwise, you might still have to enter your $guess before you see the prompt.
Here's the revised program Modern::Perl doesn't work on my system, so I use the three separate pragmas to cover it:
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
use constant {
UPPER_RANGE => 100,
MAX_GUESSES => 8,
};
$| = 1;
my $target = int ( ( rand UPPER_RANGE ) + 1 );
for my $counter (1..MAX_GUESSES) {
print "Enter guess #$counter: ";
chomp ( my $guess = <STDIN> );
if ($guess == $target) {
say "Congratulations! You guessed the secret number $target in $counter turns";
exit;
}
elsif ($guess > $target) {
say "Your guess, $guess, is too high.";
}
else {
say "Your guess, $guess, is too low.";
}
}
say "You lose. The number was $target.";
A few things I didn't cover:
When doing numeric comparisons, use the == operator and not eq which is for strings.
I use say instead of print when I can. The say command is like print except it automatically puts a NL on the end for me.
Notice the exit; after I stated you won. I want to end my program at that point.
Notice I don't declare all of my variables until I need them. In my program, $guess and $counter only exists inside the for loop while $target exists both inside and outside the for loop.
I use constant to help avoid mysterious numbers. For example, 1..MAX_GUESSES helps me understand that the loop is going to my maximum guess while 1..8 doesn't explain why I'm going to 8. Also, I can simply change my constant and suddenly you have 10 changes of guessing a number between 1 and 1000.
The problem here is that you aren't explicitly initializing your variables. By default, Perl initializes the variables you create with my() to the undef value. So take a look at the following line in your script:
my ($guess,$target,$counter);
This line creates three variables, all of which are set to undef. For more details on the ins and outs of the my() operator, check out the perlsub docs. Here's a relevant quote from that doc page:
The parameter list to my() may be assigned to if desired, which allows
you to initialize your variables. (If no initializer is given for a
particular variable, it is created with the undefined value.)