Need something like open OR DIE except with chomp - perl

I'm fairly new to coding and I need a fail statement to print out as if it were an or die.
Part of my code for an example:
print "Please enter the name of the file to search:";
chomp (my $filename=<STDIN>) or die "No such file exists. Exiting program. Please try again."\n;
print "Enter a word to search for:";
chomp (my $word=<STDIN>);
I need it to do it for both of these print/chomp statements. Is there anyway to just add on to this?
Whole program:
#!/usr/bin/perl -w
use strict;
print "Welcome to the word frequency calculator.\n";
print "This program prompts the user for a file to open, \n";
print "then it prompts for a word to search for in that file,\n";
print "finally the frequency of the word is displayed.\n";
print " \n";
print "Please enter the name of the file to search:";
while (<>){
print;
}
print "Enter a word to search for:";
chomp( my $input = <STDIN> );
my $filename = <STDIN>;
my$ctr=0;
foreach( $filename ) {
if ( /\b$input\b/ ) {
$ctr++;
}
}
print "Freq: $ctr\n";
exit;

You don't need to test the filehandle read <> for success. See I/O Operators in perlop. When it has nothing to read it returns an undef, which is precisely what you want so your code knows when to stop reading.
As for removing the newline, you want to chomp separately anyway. Otherwise, once the read does return an undef you'd chomp on an undefined variable, triggering a warning.
Normally, with a filehandle $fh opened on some resource, you'd do
while (my $line = <$fh>) {
chomp $line;
# process/store input as it comes ...
}
This can be STDIN as well. If it is certainly just one line
my $filename = <STDIN>;
chomp $filename;
You don't need to test chomp against failure either. Note that it returns the number of characters that it removed, so if there was no $/ (newline typically) it legitimately returns 0.
To add, it is a very good practice to always test! As a part of that mindset, please make sure to always use warnings;, and I also strongly recommend coding with use strict;.
Update to a significant question edit
In the first while loop you do not store the filename anywhere. Given the greeting that is printed, instead of that loop you should just read the filename. Then you read the word to search for.
# print greeting
my $filename = <STDIN>;
chomp $filename;
my $input = <STDIN>;
chomp $input;
However, then we get to the bigger problem: you need to open the file, and only then can you go through it line by line and search for the word. This is where you need the test. See the linked doc page and the tutorial perlopentut. First check whether a file with that name exists.
if (not -e $filename) {
print "No file $filename. Please try again.\n";
exit;
}
open my $fh, '<', $filename or die "Can't open $filename: $!";
my $word_count = 0;
while (my $line = <$fh>)
{
# Now search for the word on a line
while ($line =~ /\b$input\b/g) {
$word_count++;
}
}
close $fh or die "Can't close filehandle: $!";
The -e above is one of the file-tests, this one checking whether the given file exists. See the doc page for file-tests (-X). In the code above we just exit with a message, but you may want to print the message prompting the user to enter another name, in a loop.
We use while and the /g modifier in regex to find all occurencies of the word on a line.
I'd like to also strongly suggest to always start your programs with
use warnings 'all';
use strict;

Related

How do I get it to stop removing what was previously in the text file?

Have a file with names, display the people in the file, then ask the person if they want to sign up, agree with Y, prompt them for their name, then add their name to the end of the file (if they say y) append the name to the end of the file using Perl.
This is the assignment, and this code below is mostly functional. However, when you input a name, it will remove all the other names that may have been in the file previously.
my $file = "name.txt";
# Use the open() function to open the file.
unless(open FILE, $file) {
# Die with error message
# if we can't open it.
die "\nUnable to open $file\n";
}
while(my $line = <FILE>) {
print $line;
}
open (fh, ">", "name.txt");
print "Do you want to sign up? \n";
$choice = <STDIN>;
$y = "yes\n";
if ($choice eq $y) {
print "Enter your name\n";
}
$a = <>;
# Writing to the file
print fh $a;
close(fh) or "Couldn't close the file";
The main problem is that you used the wrong MODE when you opened the file the second time. From open:
If MODE is >, the file is opened for output, with existing files first
being truncated ("clobbered") and nonexisting files newly created. If
MODE is >>, the file is opened for appending, again being created if
necessary.
The reason your file was overwritten was that you opened it with >, which clobbers your existing file. As the document states, you need to use >> to append lines to the end of your file.
Another problem is that, even when the user enters no instead of yes, the code prompts for a name anyway, then writes the name to the file. Unless the user inputs yes, you don't want to write to the file, so all that code belongs inside the if block.
Here is a more conventional way to write your code:
use warnings;
use strict;
my $file = 'name.txt';
open my $fh, '<', $file or die "\nUnable to open $file: $!\n";
while (my $line = <$fh>) {
print $line;
}
close $fh;
print "Do you want to sign up? \n";
my $choice = <STDIN>;
my $y = "yes\n";
if ($choice eq $y) {
print "Enter your name\n";
my $name = <>;
open $fh, '>>', $file;
print $fh $name;
close $fh;
}
It is important to use strict and warnings. There are benefits to using a lexical filehandle ($fh) instead of bare filehandles (FILE). It is good practice to explicitly use the mode for open, even with opening for input (<).
The way you open the file, you open it to write (>), not append(>>).
Change
open (fh, ">", "name.txt");
to include >> and not >:
open (fh, ">>", "name.txt");
Also, Perl Appending, appends text to the bottom of the file.
If you would want to append to the start of the file then check out this answer.

Need suggestion with 'if' statement

I need help adjusting this block of my code. Everything was working but then it quit working and fails every time (prints). What am I doing wrong?
print "Enter a word to search for:";
chomp (my $word = <STDIN> );
if (not -e $word){
print "No such word found.\n";
exit;
}
Whole program.
#!/usr/bin/perl -w
use strict;
print "Welcome to the word frequency calculator.\n";
print "This program prompts the user for a file to open, \n";
print "then it prompts for a word to search for in that file,\n";
print "finally the frequency of the word is displayed.\n";
print " \n";
print "Please enter the name of the file to search:";
chomp (my $filename = <STDIN> );
if (not -e $filename){
print "No such file exists. Exiting program. Please try again.
+\n";
exit;
}
print "Enter a word to search for:";
chomp (my $word = <STDIN> );
if (not -e $word){
print "No such word found.\n";
exit;
}
print "Frequency of word: " . grep $word eq $_,
split /\W+/i, do { local (#ARGV, $/)= $filename; <> };
exit;
print "Welcome to the word frequency calculator.\n";
print "This program prompts the user for a file to open, \n";
print "then it prompts for a word to search for in that file,\n";
print "finally the frequency of the word is displayed.\n";
print " \n";
So, according to that, this program will...
Ask the user for a file to search.
Ask the user for a word to search for.
Check how often that word is in that file.
You have the first part down.
print "Please enter the name of the file to search:";
chomp (my $filename = <STDIN> );
if (not -e $filename){
print "No such file exists. Exiting program. Please try again.\n";
exit;
}
Though it can be done a bit more succinctly by using die instead of print + exit. And generally rather than checking to see if a file exists you should just try to open a file. A file might exist but not be readable. Or it might exist when you checked and then be deleted when you later try to open it.
print "Please enter the name of the file to search: ";
chomp (my $filename = <STDIN> );
open my $fh, "<", $filename or die "Sorry, couldn't open $filename because $!";
Then for the second bit, you only need to prompt for the word. Checking whether the word exists as a filename is nonsense.
print "Enter a word to search for: ";
chomp (my $word = <STDIN> );
And, finally, reading the file and finding the word frequency. The code you're using for that is very hard to understand...
print "Frequency of word: " . grep $word eq $_,
split /\W+/i, do { local (#ARGV, $/)= $filename; <> };
...it also slurps the whole file into memory which is inefficient if the file gets large.
Instead, read files line by line with a while loop. And instead of splitting the line into words, search through the line with /\Q$word\E/g. /g says to continue the search from the last place you matched.
my $frequency = 0;
while( my $line = <$fh> ) {
while( $line =~ /\Q$word\E/g ) {
$frequency++
}
}
See perlretut for more info.

How do I find the line a word is on when the user enters text in Perl?

I have a simple text file that includes all 50 states. I want the user to enter a word and have the program return the line the specific state is on in the file or otherwise display a "word not found" message. I do not know how to use find. Can someone assist with this? This is what I have so far.
#!/bin/perl -w
open(FILENAME,"<WordList.txt"); #opens WordList.txt
my(#list) = <FILENAME>; #read file into list
my($state); #create private "state" variable
print "Enter a US state to search for: \n"; #Print statement
$line = <STDIN>; #use of STDIN to read input from user
close (FILENAME);
An alternative solution that reads only the parts of the file until a result is found, or the file is exhausted:
use strict;
use warnings;
print "Enter a US state to search for: \n";
my $line = <STDIN>;
chomp($line);
# open file with 3 argument open (safer)
open my $fh, '<', 'WordList.txt'
or die "Unable to open 'WordList.txt' for reading: $!";
# read the file until result is found or the file is exhausted
my $found = 0;
while ( my $row = <$fh> ) {
chomp($row);
next unless $row eq $line;
# $. is a special variable representing the line number
# of the currently(most recently) accessed filehandle
print "Found '$line' on line# $.\n";
$found = 1; # indicate that you found a result
last; # stop searching
}
close($fh);
unless ( $found ) {
print "'$line' was not found\n";
}
General notes:
always use strict; and use warnings; they will save you from a wide range of bugs
3 argument open is generally preferred, as well as the or die ... statement. If you are unable to open the file, reading from the filehandle will fail
$. documentation can be found in perldoc perlvar
Tool for the job is grep.
chomp ( $line ); #remove linefeeds
print "$line is in list\n" if grep { m/^\Q$line\E$/g } #list;
You could also transform your #list into a hash, and test that, using map:
my %cities = map { $_ => 1 } #list;
if ( $cities{$line} ) { print "$line is in list\n";}
Note - the above, because of the presence of ^ and $ is an exact match (and case sensitive). You can easily adjust it to support fuzzier scenarios.

Print email addresses to a file in Perl

I have been scouring this site and others to find the best way to do what I need to do but to no avail. Basically I have a text file with some names and email addresses. Each name and email address is on its own line. I need to get the email addresses and print them to another text file. So far all I have been able to print is the "no email addresses found" message. Any thoughts? Thanks!!
#!/usr/bin/perl
open(IN, "<contacts.txt") || die("file not found");
#chooses the file to read
open(OUT, ">emailaddresses.txt");
#prints file
$none = "No emails found!";
$line = <IN>;
for ($line)
{
if ($line =~ /[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}/g)
{
print (OUT $line);
}
else
{
print (OUT $none);
}
}
close(IN);
close(OUT);
First, always use strict; use warnings. This helps writing correct scripts, and is an invaluable aid when debugging.
Also, use a three-arg-open:
open my $fh, "<", $filename or die qq(Can't open "$filename": $!);
I included the reason for failure ($!), which is a good practice too.
The idiom to read files (on an open filehandle) is:
while (<$fh>) {
chomp;
# The line is in $_;
}
or
while (defined(my $line = <$fh>)) { chomp $line; ... }
What you did was to read one line into $line, and loop over that one item in the for loop.
(Perl has a notion of context. Operators like <$fh> behave differently depending on context. Generally, using a scalar variable ($ sigil) forces scalar context, and #, the sigil for arrays, causes list context. This is quite unlike PHP.)
I'd rewrite your code like:
use strict; use warnings;
use feature 'say';
my $regex = qr/[A-Z0-9._%+-]+\#[A-Z0-9.-]+\.[A-Z]{2,4}/i; # emails are case insensitive
my $found = 0;
while (<>) { # use special ARGV filehandle, which usually is STDIN
while (/($regex)/g) {
$found++;
say $1;
}
}
die "No emails found\n" unless $found;
Invoked like perl script.pl <contacts.txt >emailaddresses.txt. The shell is your friend, and creating programs that can be piped from and to is good design.
Update
If you want to hardcode the filenames, we would combine the above script with the three-arg open I have shown:
use strict; use warnings; use feature 'say';
use autodie; # does `... or die "Can't open $file: $!"` for me
my $regex = qr/[A-Z0-9._%+-]+\#[A-Z0-9.-]+\.[A-Z]{2,4}/i;
my $found = 0;
my $contact_file = "contacts.txt";
my $email_file = "emailaddresses.txt";
open my $contact, "<", $contact_file;
open my $email, ">", $email_file;
while (<$contact>) { # read from the $contact filehandle
while (/($regex)/g) { # the /g is optional if there is max one address per line
$found++;
say {$email} $1; # print to the $email file handle. {curlies} are optional.
}
}
die "No emails found\n" unless $found; # error message goes to STDERR, not to the file

Perl: Getting lines from file based on user input [duplicate]

This question already has answers here:
Closed 12 years ago.
Possible Duplicate:
How can I implement Unix grep in Perl?
Is there anyway I can write a perl script to print out all lines in a file that contains the string input by the user?
There are two separate concepts in this answer:
File IO, and iterating through all lines in a while
Regular expression, and especially passing a variable to a regular expression.
Note the use of quotemeta. It is important when the user input contains RE specific characters (which may even create an illigel RE if you don't handle it).
Here is the code:
print "Looking for: ";
my $input = <>;
chomp $input;
my $re = quotemeta $input;
open my $fh, "<", "myfile.txt" or die $!;
while( <$fh> ) {
print if /$re/;
}
close $fh;
You add the regular expression modifer "i"
print "Name: ";
my $string = <>;
open FILE, "test.txt" or die $!;
while(<FILE>) {
chomp;
print "$_\n" if(/$string*/i);
}
close FILE;
Oh, figured out how to do it. Turned out to be quite simple
But how do I do it if I don't want the search to be case sensitive?
print "Name: ";
my $string = <>;
open FILE, "test.txt" or die $!;
while(<FILE>) {
chomp;
print "$_\n" if(/$string*/);
}
close FILE;
Your while loop body is a bit complex. This also works:
while (<FILE>) { print if /$string/i }
There's no need to chomp the newline and then add it back in.
One liner:
perl -nE 'BEGIN{$pattern=shift};say if /$pattern/i' pattern filename(s)
or
perl -nE 'BEGIN{$pattern=shift};say "$ARGV $.: $_" if /$pattern/i' pattern filename(s)
to get the file and line number too.