Filehandle open() and the split variable - perl

I am a beginner in Perl.
What I do not understand is the following:
To write a script that can:
Print the lines of the file $source with a comma delimiter.
Print the formatted lines to an output file.
Allow this output file to be specified in command-line.
Code:
my ( $source, $outputSource ) = #ARGV;
open( INPUT, $source ) or die "Unable to open file $source :$!";
Question: I do not understand how one can specify in the command line, upon starting to write the code the text of the output file.

I would rely on redirection operator in the shell instead, such as:
script.pl input.txt > output.txt
Then it is a simple case of doing this:
use strict;
use warnings;
while (<ARGV>) {
s/\n/,/;
print;
}
Then you can even merge several files with script.pl input1.txt input2.txt ... > output_all.txt. Or just do one file at the time, with one argument.

If I understood your question right I hope this example can help.
Program:
use warnings;
use strict;
## Check input and output file as arguments in command line.
die "Usage: perl $0 input-file output-file\n" unless #ARGV == 2;
my ( $source, $output_source ) = #ARGV;
## Open both files, one for reading and other for writing.
open my $input, "<", $source or
die "Unable to open file $source : $!\n";
open my $output, ">", $output_source or
die "Unable to open file $output_source : $!\n";
## Read all file line by line, substitute the end of line with a ',' and print
## to output file.
while ( my $line = <$input> ) {
$line =~ tr/\n/,/;
printf $output "%s", $line;
}
close $input;
close $output;
Execution:
$ perl script.pl infile outfile

Related

Perl parse in command line input file

I would like to parse in input file using command line. I ran as below but I am getting error (could not open filename) when I ran as below: Is my code wrong or what I type on the commandline is incorrect?
commandline> perl script.pl FILENAME1.TXT
Below is my code to parse in input file:
my $filename = <STDIN>;
open (my $file, '<', $filename) or die "could not open file '$filename': $!";
my $str = do {local $/; <$file>};
close $file;
You're trying to read $filename from standard input, when it's an argument to the program. You probably want something like
my $filename = $ARGV[0]
Perl's command line arguments show up in the variable #ARGV.
my( $filename ) = #ARGV;
However, Perl also has the special ARGV filehandle the opens the files you specify on the command line
while( <ARGV> ) { ... }
Even better, ARGV is the default filehandle:
while( <> ) { ... }
And, ARGV includes standard input if you didn't specify any arguments. That means that last while works in either of these calls:
% perl script.pl filename.txt
% perl script.pl < filename.txt
In your program, you read from STDIN, which is a different thing. That's standard input and is not related to the command line arguments. That's the data you send to the program after its running. For example, you might prompt for the filename:
print "Enter the filename: ";
my $filename = <STDIN>;
chomp( $filename );

To trim lines based on line number in perl

My Perl file generates the text file which usually contains 200 lines. Sometimes it exceeds 200 lines (For example 217 lines). I need to trim off the rest of the lines from the 201st line. I have used the counter method to trim the exceeded lines. Is there any other simple and efficient way to do this?
Code:
#!/usr/bin/perl -w
use strict;
use warnings;
my $filename1="channel.txt";
my $filename2="channel1.txt";
my $fh;
my $fh1;
my $line;
my $line1;
my $count=1;
open $fh, '<', $filename1 or die "Can't open > $filename1: $!";
open $fh1, '>', $filename2 or die "Can't open > $filename2: $!";
while(my $line = <$fh>)
{
chomp $line;
chomp $line1;
if($count<201)
{
print $fh1 "$line\n";
}
$count++;
}
close ($fh1);
close($fh);
I have already mentioned in my comment, this is short version of that comment If you actually trying to trim the file you can use the Perl One Liner instead of writing the whole code
perl -pe 'last if($. == 201);' input.text >result.txt
-p used for process the file line by line an print the output
-e execute flag, to execute the Perl syntax
With Perl script you can do this also
open my $fh,"<","input.txt";
open my $wh,">","result.txt";
print $wh scalar <$fh> for(1..10);
xxfelixxx already gave you the correct answer. I am just changing my earlier posted answer, to clean up your code and to write back to the original file:
use strict;
use warnings;
my #array;
my $filename="channel.txt";
open my $fh, '<', $filename or die "Can't open > $filename: $!";
while( my $line = <$fh> ) {
last if $. > 200;
push #array, $line;
}
close($fh);
open $fh, '>', $filename or die "Can't open > $filename: $!";
print $fh #array;
close($fh);
There is no need to keep your own counter, perl has a special variable $. which keeps track of the input line number. You can simplify your loop like so:
while( chomp( my $line = <$fh> ) ) {
last if $. > 200;
print $fh1 "$line\n";
}
perldoc perlvar - Search for INPUT_LINE_NUMBER.
To write back to the original file: input.txt without using redirection:
perl -pi.tmp -we "last if $.>200;" input.txt
where
-i : opens a temp file and automatically replaces the file to be
edited with the temporary file after processing (the '.tmp'
is the suffix to use for the temp file during processing)
-w : command line flag to 'use warnings'
-p : magic; basically equivalent to coding:
LINE: while (defined $_ = <ARGV>)) {
"your code here"
}
-e : perl code follows this flag (enclosed in double quotes for MSWin32 aficiandos)

Open two text files, process them and write to separate files

I'm using with Perl to open two text files, process them and then write the output to another file.
I have a file INPUT were every line is a customer. I will process each line into variables that will be used to substitute text in another file, TEMP. The result should be written into individual files for each customer, OUTPUT.
My program seems to be working on only the first file. The rest of the files remain empty with no output.
#!/usr/bin/perl -w
if ( $#ARGV < 0) {
print "Usage: proj5.pl <mm/dd/yyyy>\n";
exit;
}
my $date = $ARGV[0];
open(INFO, "p5Customer.txt") or die("Could not open p5Customer.txt file\n");
open(TEMP, "template.txt") or die("Could not open template.txt file\n");
my $directory = "Emails";
mkdir $directory unless(-e $directory);
foreach $info (<INFO>){
($email, $fullname, $title, $payed, $owed) = split /,/, $info;
next if($owed < $payed);
chomp($owed);
$filepath = "$directory/$email";
unless(open OUTPUT, '>>'.$filepath){
die "Unable to create '$filepath'\n";
}
foreach $detail (<TEMP>){
$detail =~ s/EMAIL/$email/g;
$detail =~ s/(NAME|FULLNAME)/$fullname/g;
$detail =~ s/TITLE/$title/g;
$detail =~ s/AMOUNT/$owed/g;
$detail =~ s{DATE}{$date}g;
print OUTPUT $detail;
}
close(OUTPUT);
}
close(INFO);
close(TEMP);
As has been said, you need to open your template file again each time you read from it. There's a bunch of other issues with your code too
Always use strict and use warnings 'all' and declare every variable with my as close as possible to where it is first used
$#ARGV is the index of the last element of #ARGV, so $#ARGV < 0 is much better written as #ARGV < 1
You should use lexical file handles, and the three-parameter form of open, so open(INFO, "p5Customer.txt") should be open my $info_fh, '<', "p5Customer.txt"
You should use while instead of for to read from a file
It is easier to use the default variable $_ for short loops
It is pointless to capture a substring in a regular expression if you're not going to use it, so (NAME|FULLNAME) should be NAME|FULLNAME
There is no point in closing input files before the end of your program
It is also much better to use an existing template system, such as
Template::Toolkit
This should work for you
#!/usr/bin/perl
use strict;
use warnings 'all';
if ( #ARGV < 1 ) {
print "Usage: proj5.pl <mm/dd/yyyy>\n";
exit;
}
my $date = $ARGV[0];
open my $info_fh, '<', 'p5Customer.txt' or die qq{Could not open "p5Customer.txt" file: $!};
my $directory = "Emails";
mkdir $directory unless -e $directory;
while ( <$info_fh> ) {
chomp;
my ($email, $fullname, $title, $payed, $owed) = split /,/;
next if $owed < $payed;
open my $template_fh, '<', 'template.txt' or die qq{Could not open "template.txt" file: $!};
my $filepath = "$directory/$email";
open my $out_fh, '>', $filepath or die qq{Unable to create "$filepath": $!};
while ( <$template_fh> ) {
s/EMAIL/$email/g;
s/FULLNAME|NAME/$fullname/g;
s/TITLE/$title/g;
s/AMOUNT/$owed/g;
s/DATE/$date/g;
print $out_fh $_;
}
close($out_fh);
}
Your problem is that the TEMP loop is inside the INPUT loop and so the TEMP loop will end while the INPUT loop is still on the first line of the INPUT file.
Best to store TEMP file data into a hash table and work on the TEMP hash table inside the INPUT loop.
Good luck.

Search a word in file and replace in Perl

I want to replace word "a" to "red" in a.text files. I want to edit the same file so I tried this code but it does not work. Where am I going wrong?
#files=glob("a.txt");
foreach my $file (#files)
{
open(IN,$file) or die $!;
<IN>;
while(<IN>)
{
$_=~s/a/red/g;
print IN $file;
}
close(IN)
}
I'd suggest it's probably easier to use perl in sed mode:
perl -i.bak -p -e 's/a/red/g' *.txt
-i is inplace edit (-i.bak saves the old as .bak - -i without a specifier doesn't create a backup - this is often not a good idea).
-p creates a loop that iterates all the files specified one line at a time ($_), applying whatever code is specified by -e before printing that line. In this case - s/// applies a sed-style patttern replacement to $_, so this runs a search and replace over every .txt file.
Perl uses <ARVG> or <> to do some magic - it checks if you specify files on your command line - if you do, it opens them and iterates them. If you don't, it reads from STDIN.
So you can also do:
somecommand.sh | perl -i.bak -p -e 's/a/red/g'
In your code you are using same filehandle to write which you have used for open the file to reading. Open the same file for write mode and then write.
Always use lexical filehandle and three arguments to open a file. Here is your modified code:
use warnings;
use strict;
my #files = glob("a.txt");
my #data;
foreach my $file (#files)
{
open my $fhin, "<", $file or die $!;
<$fhin>;
while(<$fhin>)
{
$_ =~ s/\ba\b/red/g;
push #data, $_;
}
open my $fhw, ">", $file or die "Couldn't modify file: $!";
print $fhw #data;
close $fhw;
}
Here is another way (read whole file in a scalar):
foreach my $file (glob "/path/to/dir/a.txt")
{
#read whole file in a scalar
my $data = do {
local $/ = undef;
open my $fh, "<", $file or die $!;
<$fh>;
};
$data =~ s/\ba\b/red/g; #replace a with red,
#modify the file
open my $fhw, ">", $file or die "Couldn't modify file: $!";
print $fhw $data;
close $fhw;
}

read two text files as an argument and display it's contents using perl

I have two text files and I want to read them by passing argument at command line.
Now how to take second file? When I give the second file name command line is not reading. Please suggest.
I have used $ARGV[0] and $ARGV[1] in the code to pass the arguments at command line.
$ ./read.pl file1 file2
Reading file1
Reading file2
$ cat read.pl
#!/usr/bin/perl
use strict;
use warnings;
readFile($_) for #ARGV;
sub readFile {
my $filename = shift;
print "Reading $filename\n";
#OPEN CLOSE stuff here
}
my ($file1, $file2) = #ARGV;
open my $fh1, '<', $file1 or die $!;
open my $fh2, '<', $file2 or die $!;
while (<$fh1>) {
do something with $_
}
while (<$fh2>) {
do something with $_
}
close $fh1;
close $fh2;
Where $_ is the default variable.
run as:
perl readingfile.pl filename1 filename2