I'm trying to use to read in user input to a programme as follows:
#!/usr/bin/perl -w
use strict;
if ($#ARGV == 0) {
print "What condition are you sorting?\t";
chomp(my $condition = <STDIN>);
# Use $condition in further blocks of code...
}
This is working. However When I can't work out how to enter 2 (or more) values to be used in a similar fashion. E.g
if ($#ARGV == 1) {
print "What conditions are you comparing?\t";
chomp(my $condition1 = <STDIN>);
chomp(my $condition2 = <STDIN>);
Allows me to input twice, but the formatting is distorted:
What conditions are you comparing? <condition1>
<condition2>
You can enter conditions separated by comma or white space to preserve formatting,
chomp(my $input = <STDIN>);
my ($condition1, $condition2) = split /[\s,]+/, $input;
Related
I am attempting to create a Perl script that filters data presented on STDIN, changing all occurrences of
one string to another and outputting all input lines, changed and unchanged to STDOUT. FROMSTRING and TOSTRING can be PERL-compatible regular expressions. I am unable to get matching output.
Here is an example of what I am trying to achieve.
echo "Today is Saturday" | f.pl 'a' '#'
Output Tod#y is S#turd#y.
echo io | filter.pl '([aeiou])([aeiou])' '$2$1'
Output oi.
#!/usr/bin/perl
use strict;
use warnings;
if (#ARGV != 2){
print STDERR "Usage: ./filter.pl FROMSTRING TOSTRING\n"
}
exit 1;
my $FROM = $ARGV[0];
my $TO = $ARGV[1];
my $inLine = "";
while (<STDIN>){
$inLine = $_;
$inLine =~ s/$FROM/$TO/;
print $inLine
}
exit 0;
First off, the replacement part of a s/.../.../ operation is not a regex; it works like a double-quoted string.
There are a couple of issues with your code.
Your exit 1; statement appears in the middle of the main code, not in the error block. You probably want:
if (#ARGV != 2) {
print STDERR "Usage: ./filter.pl FROMSTRING TOSTRING\n";
exit 1;
}
You're missing a g flag if you want multiple substitutions to happen in the same line:
$inLine =~ s/$FROM/$TO/g;
There's no need to predeclare $inLine; it's only used in one block.
There's also no need to read a line into $_ just to copy it into $inLine.
It's common to use $names_like_this for variables and functions, not $namesLikeThis.
You can use $0 instead of hardcoding the program name in the error message.
exit 0; is redundant at the end.
The following is closer to how I'd write it:
#!/usr/bin/perl
use strict;
use warnings;
if (#ARGV != 2) {
die "Usage: $0 FROMSTRING TOSTRING\n";
}
my ($from, $to) = #ARGV;
while (my $line = readline STDIN) {
$line =~ s/$from/$to/g;
print $line;
}
That said, none of this addresses your second example with '$2$1' as the replacement. The above code won't do what you want because $to is a plain string. Perl won't scan it to look for things like $1 and replace them.
When you write "foo $bar baz" in your code, it means the same thing as 'foo ' . $bar . ' baz', but this only applies to code, i.e. stuff that literally appears in your source code. The contents of $bar aren't re-scanned at runtime to expand e.g. \n or $quux. This also applies to $1 and friends, which are just normal variables.
So how do you get '$2$1' to work?
One way is to mess around with eval, but I don't like it because, well, it's eval: If you're not very careful, it would allow someone to execute arbitrary code by passing the right replacement "string".
Doing it without eval is possible and even easy with e.g. Data::Munge::replace:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Munge qw(replace);
if (#ARGV != 2) {
die "Usage: $0 FROMSTRING TOSTRING\n";
}
my ($from, $to) = #ARGV;
while (my $line = readline STDIN) {
print replace($line, $from, $to, 'g');
}
replace works like JavaScript's String#replace in that it expands special $ sequences.
Doing it by hand is also possible but slightly annoying because you basically have to treat $to as a template and expand all $ sequences by hand (e.g. by using another regex substitution):
# untested
$line =~ s{$from}{
my #start = #-;
my #stop = #+;
(my $r = $to) =~ s{\$([0-9]+|\$)}{
$1 eq '$'
? '$'
: substr($from, $start[$1], $stop[$1] - $start[$1])
}eg;
$r
}eg;
(This does not implement braced groups such as ${1}, ${2}, etc. Those are left as an exercise for the reader.)
This code is sufficiently annoying to write (and look at) that I much prefer using a module like Data::Munge for this sort of thing.
three errors found:
; after error message
exit 1;
$inLine =~ s/$FROM/$TO/g;
like:
#!/usr/bin/perl
use strict;
use warnings;
if (#ARGV != 2){
print STDERR "Usage: ./filter.pl FROMSTRING TOSTRING\n";
exit 1;
}
my $FROM = $ARGV[0];
my $TO = $ARGV[1];
my $inLine = "";
while (<STDIN>){
$inLine = $_;
$inLine =~ s/$FROM/$TO/g;
print $inLine
}
exit 0;
(New to perl)
I have a small perl program that calculates factorials. I'd like to use a while loop so that after the user gets a result, they will be asked "Calculate another factorial? Y/N" and have Y run the code again & have N end the program.
Here's my code:
print"Welcome! Would you like to calculate a factorial? Y/N\n";
$decision = <STDIN>;
while $decision == "Y";
{
print"Enter a positive # more than 0: \n";
$num = <STDIN>;
$fact = 1;
while($num>1)
{
$fact = $fact * $num;
$num $num - 1;
}
print $fact\n;
print"Calculate another factorial? Y/N\n";
$decision = <STDIN>;
}
system("pause");
What's giving me trouble is where to put the while loop and how to make the Y/N option work. I'm also unclear about system("pause") and sleep functions. I do know that system("pause") makes my programs work though.
Your program is almost right, just a few issues:
Please get used to always add use strict; and use warnings; to your scripts. They will
(beyond other things) force you to declare all the variables you use (with my $num=…;)
and warn you about common errors (like typos). Some people consider it a bug that
use strict; and use warnings; aren't turned on by default.
When reading a line from STDIN (or some other filehandle) the read line will contain the
trailing newline character "\n". For your comparison to work you must get rid of that using
the chomp function.
There are two different sets of comparison operators in Perl: one for strings and one for numbers.
Numbers are compared with <, >, <=, >=, ==, and !=. For strings you must use
lt (less-than), gt, le (less-or-equal), ge, eq, and ne. If you use one of the number
operators on strings Perl will try to interpret your string as a number, so $decision == "Y"
would check whether $decision is 0. If you had use warnings; Perl would have noticed you.
Use $decision eq "Y" instead.
The outer while loop had a trailing ; just after the comparison which will give you an endless
loop or a no-op (depending on the content of $decision).
You forgot the = in $num = $num - 1;.
You forgot the quotes " around print "$fact\n";
system("pause") only works on Windows where pause is an external command. On Linux (where
I just tested) there is no such command and system("pause") fails with command not found.
I replaced it with sleep(5); which simply waits 5 seconds.
.
#!/usr/bin/env perl
use strict;
use warnings;
print "Welcome! Would you like to calculate a factorial? Y/N\n";
my $decision = <STDIN>;
chomp($decision); # remove trailing "\n" from $decision
while ( $decision eq 'Y' ) {
print "Enter a positive # more than 0: \n";
my $num = <STDIN>;
chomp($num);
my $fact = 1;
while ( $num > 1 ) {
$fact = $fact * $num;
$num = $num - 1;
}
print "$fact\n";
print "Calculate another factorial? Y/N\n";
$decision = <STDIN>;
chomp($decision);
}
print "ok.\n";
sleep(5); # wait 5 seconds
Always add use warnings and use strict to the beginning of your program.
There are a number of typos in your code that would have been caught by this.
#!/usr/bin/perl
use warnings;
use strict;
print "Welcome! Would you like to calculate a factorial? Enter 'Y' or 'N': ";
my $answer = <STDIN>;
chomp($answer);
while($answer =~ /^[Yy]$/){
my $fact = 1;
print"Enter a positive number greater than 0: ";
my $num = <STDIN>;
chomp($num);
my $number_for_printing = $num;
while($num > 0){
$fact = $fact * $num;
$num--;
}
print "The factorial of $number_for_printing is: $fact\n";
print"Calculate another factorial? Enter 'Y' or 'N': ";
$answer = <STDIN>;
chomp($answer);
}
print "Goodbye!\n";
I am a beginner to perl and have just been messing around trying to create little scripts. I'm not sure what is wrong here but it just falls through to the else every time as if nothing I input satisfies the if or elsif conditions. Is it because eq is the wrong operator? Or is there something else wrong in my code? Thanks!
#!/usr/bin/perl
use strict;
use warnings;
print "what is your name?\n";
my $name = readline STDIN;
print "Hello $name How are you today?\n";
my $feeling = readline STDIN;
if ($feeling eq "happy") {
print "that's good!\n";
}
elsif ($feeling eq "good") {
print "okay!\n";
}
else {
print "Interesting\n";
}
Use chomp($feeling);
#!/usr/bin/perl
use strict;
use warnings;
print "what is your name?\n";
my $name = readline STDIN;
chomp($name);
print "Hello $name How are you today?\n";
my $feeling = readline STDIN;
chomp($feeling);
if ($feeling eq "happy") {
print "that's good!\n";
}
elsif ($feeling eq "good") {
print "okay!\n";
}
else {
print "Interesting\n";
}
readline STDIN captures every character typed along with last enter hit as \n, say if you type "happy" and hit enter for $feeling then its accepted as "happy\n" notice \n is because enter hit to remove last \n newline character use chomp removes any trailing string
chomp is used to "chomp off" the input record separator, which by default is a newline character.
#!/usr/bin/perl
use strict;
use warnings;
use 5.012; # to use things like 'say' and 'given'
say "what is your name?"; # 'say' is like 'print', but means you don't have to use '\n'
my $name = <STDIN>; # good to include angled brackets <>
chomp($name); # remove the newline when entering the number
say qq{Hello $name, how are you today?}; # qq{} acts like double-quotes ("")
my $feeling = <STDIN>;
chomp $feeling; # notice parenthese aren't always needed
# you could also do chomp(my $feeling=<STDIN>);
given (lc $feeling){ # 'given' is Perl's version of a Switch and lc makes input lowercase
when('happy') { say q{That's good.} } # q{} acts like single-quotes ('')
when('good') { say q{Okay!} }
default { say q{Interesting} } # your else-case
}
As the warnings suggest, given is experimental until smartmatch is figured out. It is perfectly acceptable to use the if-elsif-else structure, if you choose.
My script needs to get a series of numbers input by the user and find the average of them. I would like to use the line 'end-of-file' to show that the user is done inputting code. Any help would be appreciated. Below is what I have so far. I think I am really close, but I am missing something.
Code:
#! /usr/bin/perl
use 5.010;
print "Enter the scores and type end-of-file when done";
chomp(#scores = <STDIN>);
foreach (#scores) {
push_average(total(#scores));
}
sub total {
my $sum;
foreach (#_) {
$sum += $_;
}
sum;
}
sub average {
if (#_ == 0) {return}
my $count = #_;
my $sum = total(#_);
$sum/$count;
}
sub push_average {
my $average = average(#_);
my #list;
push #list, $average;
return #list;
}
You are quite close. Adding use strict; use warnings at the top of every Perl script will alert you of errors that might go unnoticed otherwise.
A few hints:
You forgot the sigil of $sum in the last statement of total. Currently, you return a string "sum" (without strict vars), or possibly call a sub called sum.
You don't need the foreach in the main part, rather do
my #averages = push_average(#scores);
The total is already calculated inside push_average
You probably want to print out the resulting average:
my $avg = $averages[0];
say "The average of these numbers is $avg";
The push_average is silly; you return a new array of one element. You could return that one element just as well.
Suggested script:
use strict; use warnings; use 5.010;
use List::Util qw/sum/; # very useful module
# say is like print, but appends a newline. Available with 5.10+
say "Please enter your numbers, finish with Ctrl+D";
my #nums = <STDIN>;
chomp #nums;
# The // is the defined-or operator
# interpolating undef into a string causes a warning.
# Instead, we give an expressive message:
my $avg = average(#nums) // "undefined";
say "The average was $avg";
sub average { #_ ? sum(#_) / #_ : undef } # return undef value if called without args
reads up to the newline. You've got a few choices here. You can ask the user to input the numbers separated by spaces and then split it into your #choices array. Or you can keep asking them to enter a number or just hit enter to finish.
Answer 1)
print "Enter scores separated by a space and press enter when done";
chomp($input = <STDIN>);
#choices = split(' ', $input);
Answer 2)
#chomp = ();
do {
print "Enter a score and then press enter. If done, just press enter.";
chomp($temp = <STDIN>);
if($trim ne '') {
push(#choices, $temp);
}
} until ($temp eq '');
I am using perl to search for a specific strings in a file with different sequences listed under different headings. I am able to write script when there is one sequence present i.e one heading but am not able to extrapolate it.
suppose I am reqd to search for some string "FSFSD" in a given file then eg:
can't search if file has following content :
Polons
CACAGTGCTACGATCGATCGATDDASD
HCAYCHAYCHAYCAYCSDHADASDSADASD
Seliems
FJDSKLFJSLKFJKASFJLAKJDSADAK
DASDNJASDKJASDJDSDJHAJDASDASDASDSAD
Teerag
DFAKJASKDJASKDJADJLLKJ
SADSKADJALKDJSKJDLJKLK
Can search when file has one heading i.e:
Terrans
FDKFJSKFJKSAFJALKFJLLJ
DKDJKASJDKSADJALKJLJKL
DJKSAFDHAKJFHAFHFJHAJJ
I need to output the result as "String xyz found under Heading abc"
The code I am using is:
print "Input the file name \n";
$protein= <STDIN>;
chomp $protein;
unless (open (protein, $protein))
{
print "cant open file \n\n";
exit;
}
#prot= <protein>;
close protein;
$newprotein=join("",#prot);
$protein=~s/\s//g;
do{
print "enter the motif to be searched \n";
$motif= <STDIN>;
chomp $motif;
if ($protein =~ /motif/)
{
print "found motif \n\n";
}
else{
print "not found \n\n";
}
}
until ($motif=~/^\s*$/);
exit;
Seeing your code, I want to make a few suggestions without answering your question:
Always, always, always use strict;. For the love of whatever higher power you may (or may not) believe in, use strict;.
Every time you use strict;, you should use warnings; along with it.
Also, seriously consider using some indentation.
Also, consider using obviously different names for different variables.
Lastly, your style is really inconsistent. Is this all your code or did you patch it together? Not trying to insult you or anything, but I recommend against copying code you don't understand - at least try before you just copy it.
Now, a much more readable version of your code, including a few fixes and a few guesses at what you may have meant to do, follows:
use strict;
use warnings;
print "Input the file name:\n";
my $filename = <STDIN>;
chomp $filename;
open FILE, "<", $filename or die "Can't open file\n\n";
my $newprotein = join "", <FILE>;
close FILE;
$newprotein =~ s/\s//g;
while(1) {
print "enter the motif to be searched:\n";
my $motif = <STDIN>;
last if $motif =~ /^\s*$/;
chomp $motif;
# here I might even use the ternary ?: operator, but whatever
if ($newprotein =~ /$motif/) {
print "found motif\n\n";
}
else {
print "not found\n\n";
}
}
The main issue is how do you distinguish between a header and the data, from your examples I assume that a line is a header iff it contains a lower case letter.
use strict;
use warnings;
print "Enter the motif to be searched \n";
my $motif = <STDIN>;
chomp($motif);
my $header;
while (<>) {
if(/[a-z]/) {
$header = $_;
next;
}
if (/$motif/o) {
print "Found $motif under header $header\n";
exit;
}
}
print "$motif not found\n";
So you are saying you are able to read one line and achieve this task. But when you have more than one line in the file you are not able to do the same thing?
Just have a loop and read the file line by line.
$data_file="yourfilename.txt";
open(DAT, '<', $data_file) || die("Could not open file!");
while( my $line = <DAT>)
{
//same command that you do for one 'heading' will go here. $line represents one heading
}
EDIT: You're posted example has no clear delimiter, you need to find a clear division between your headings and your sequences. You could use multiple linebreaks or a non-alphanumeric character such as ','. Whatever you choose, let WHITESPACE in the following code be equal to your chosen delimiter. If you are stuck with the format you have, you will have to change the following grammar to disregard whitespace and delimit through capitalization (makes it slightly more complex).
Simple way ( O(n^2)? ) is to split the file using a whitespace delimiter, giving you an array of headings and sequences( heading[i] = split_array[i*2], sequence[i] = split_array[i*2+1]). For each sequence perform your regex.
Slightly more difficult way ( O(n) ), given a BNF grammar such as:
file: block
| file block
;
block: heading sequence
heading: [A-Z][a-z]
sequence: [A-Z][a-z]
Try recursive decent parsing (pseudo-code, I don't know perl):
GLOBAL sequenceHeading, sequenceCount
GLOBAL substringLength = 5
GLOBAL substring = "FSFSD"
FUNC file ()
WHILE nextChar() != EOF
block()
printf ( "%d substrings in %s", sequenceCount, sequenceHeading )
END WHILE
END FUNC
FUNC block ()
heading()
sequence()
END FUNC
FUNC heading ()
in = popChar()
IF in == WHITESPACE
sequenceHeading = tempHeading
tempHeading = ""
RETURN
END IF
tempHeading &= in
END FUNC
FUNC sequence ()
in = popChar()
IF in == WHITESPACE
sequenceCount = count
count = 0
i = 0
END IF
IF in == substring[i]
i++
IF i > substringLength
count++
END IF
ELSE
i = 0
END IF
END FUNC
For detailed information on recursive decent parsing, check out Let's Build a Compiler or Wikipedia.
use strict;
use warnings;
use autodie qw'open';
my($filename,$motif) = #ARGV;
if( #ARGV < 1 ){
print "Please enter file name:\n";
$filename = <STDIN>;
chomp $filename;
}
if( #ARGV < 2 ){
print "Please enter motif:\n";
$motif = <STDIN>;
chomp $motif;
}
my %data;
# fill in %data;
{
open my $file, '<', $filename;
my $heading;
while( my $line = <$file> ){
chomp $line;
if( $line ne uc $line ){
$heading = $line;
next;
}
if( $data{$heading} ){
$data{$heading} .= $line;
} else {
$data{$heading} = $line;
}
}
}
{
# protect against malicious users
my $motif_cmp = quotemeta $motif;
for my $heading ( keys %data ){
my $data = $data{$heading};
if( $data =~ /$motif_cmp/ ){
print "String $motif found under Heading $heading\n";
exit 0;
}
}
die "String $motif not found anywhere in file $filename\n";
}