Perl reading and writing in files - perl

Alright, so I'm back with another question. I know in Python there is a way to read in a file without specifying which file it will be, until you are in the command prompt. So basically you can set the script up so that you can read in any file you want and don't have to go back and change the coding every time. Is there a way to do this in Perl? If so, can you write files like that too? Thanks.
This is what I have:
open (LOGFILE, "UNSUCCESSFULOUTPUT.txt") or die "Can't find file";
open FILE, ">", "output.txt" or die $!;
while(<LOGFILE>){
print FILE "ERROR in line $.\n" if (/Error/);
}
close FILE;
close LOGFILE;
This is what I have nome:
#!/usr/local/bin/perl
my $argument1 = $ARGV[0];
open (LOGFILE, "<$argument1") or die "Can't find file";
open FILE, ">>output.txt" or die $!;
while(<LOGFILE>){
print FILE "ERROR in line $.\n" if (/Error/);
}
close FILE;
close LOGFILE;
And it's still not appending...

Command line arguments are provided in #ARGV. You can do as you please with them, including passing them as file names to open.
my ($in_qfn, $out_qfn) = #ARGV;
open(my $in_fh, '<', $in_qfn ) or die $!;
open(my $out_fh, '>', $out_qfn) or die $!;
print $out_fh $_ while <$in_fh>;
But that's not a very unixy way of doing things. In unix tradition, the following will read from every file specified on the command line, one line at a time:
while (<>) {
...
}
Output is usually placed in files through redirection.
#!/usr/bin/env perl
# This is mycat.pl
print while <>;
# Example usage.
mycat.pl foo bar > baz
# Edit foo in-place.
perl -i mycat.pl foo
The only time one usually touches #ARGV is to process options, and even then, one usually uses Getopt::Long instead of touching #ARGV directly.
Regarding your code, your script should be:
#!/usr/bin/env perl
while (<>) {
print "ERROR in line $.\n" if /Error/;
}
Usage:
perl script.pl UNSUCCESSFULOUTPUT.txt >output.txt
You can get rid of perl from the command if you make script.pl executable (chmod u+x script.pl).

This is what I believe you want:
#!usr/bin/perl
my $argument1 = $ARGV[0];
open (LOGFILE, "<$argument1") or die "Can't find file";
open (FILE, ">output.txt") or die $!;
while(<LOGFILE>){
print FILE "ERROR in line $.\n" if (/Error/);
}
close FILE;
close LOGFILE;
Ran as from the command line:
> perl nameofpl.pl mytxt.txt
For appending change this line:
open (FILE, ">output.txt") or die $!;
To the remarkably similar:
open (FILE, ">>output.txt") or die $!;

I assume you are asking how to pass an argument to a perl script. This is done with the #ARGV variable.
use strict;
use warnings;
my $file = shift; # implicitly shifts from #ARGV
print "The file is: $file\n";
You can also make use of the magic of the diamond operator <>, which will open the arguments to the script as files, or use STDIN if no arguments are supplied. The diamond operator is used as a normal file handle, typically while (<>) ...
ETA:
With the code you supplied, you can make it more flexible by doing this:
use strict;
use warnings; # always use these
my $file = shift; # first argument, required
my $outfile = shift // "output.txt"; # second argument, optional
open my $log, "<", $file or die $!;
open my $out, ">", $outfile or die $!;
while (<$log>) {
print $out "ERROR in line $.\n" if (/Error/);
}
Also see ikegami's answer on how to make it more like other unix tools, e.g. accept STDIN or file arguments, and print to STDOUT.
As I commented in your earlier question, you may simply wish to use an already existing tool for the job:
grep -n Error input.txt > output.txt

Related

Counting number of lines with conditions

This is my script count.pl, I am trying to count the number of lines in a file.
The script's code :
chdir $filepath;
if (-e "$filepath"){
$total = `wc -l < file.list`;
printf "there are $total number of lines in file.list";
}
i can get a correct output, but i do not want to count blank lines and anything in the file that start with #. any idea ?
As this is a Perl program already open the file and read it, filtering out lines that don't count with
open my $fh, '<', $filename or die "Can't open $filename: $!";
my $num_lines = grep { not /^$|^\s*#/ } <$fh>;
where $filename is "file.list." If by "blank lines" you mean also lines with spaces only then chagne regex to /^\s*$|^\s*#/. See grep, and perlretut for regex used in its condition.
That filehandle $fh gets closed when the control exits the current scope, or add close $fh; after the file isn't needed for processing any more. Or, wrap it in a block with do
my $num_lines = do {
open my $fh, '<', $filename or die "Can't open $filename: $!";
grep { not /^$|^\s*#/ } <$fh>;
};
This makes sense doing if the sole purpose of opening that file is counting lines.
Another thing though: an operation like chdir should always be checked, and then there is no need for the race-sensitive if (-e $filepath) either. Altogether
# Perhaps save the old cwd first so to be able to return to it later
#my $old_cwd = Cwd::cwd;
chdir $filepath or die "Can't chdir to $filepath: $!";
open my $fh, '<', $filename or die "Can't open $filename: $!";
my $num_lines = grep { not /^$|^\s*#/ } <$fh>;
A couple of other notes:
There is no reason for printf. For all normal prints use say, for which you need use feature qw(say); at the beginning of the program. See feature pragma
Just in case, allow me to add: every program must have at the beginning
use warnings;
use strict;
Perhaps the original intent of the code in the question is to allow a program to try a non-existing location, and not die? In any case, one way to keep the -e test, as asked for
#my $old_cwd = Cwd::cwd;
chdir $filepath or warn "Can't chdir to $filepath: $!";
my $num_lines;
if (-e $filepath) {
open my $fh, '<', $filename or die "Can't open $filename: $!";
$num_lines = grep { not /^$|^\s*#/ } <$fh>;
}
where I still added a warning if chdir fails. Remove that if you really don't want it. I also added a declaration of the variable that is assigned the number of lines, with my $total_lines;. If it is declared earlier in your real code then of course remove that line here.
perl -ne '$n++ unless /^$|^#/ or eof; print "$n\n" if eof'
Works with multiple files too.
perl -ne '$n++ unless /^$|^#/ or eof; END {print "$n\n"}'
Better for a single file.
open(my $fh, '<', $filename);
my $n = 0;
for(<$fh>) { $n++ unless /^$|^#/}
print $n;
Using sed to filter out the "unwanted" lines in a single file:
sed '/^\s*#/d;/^\s*$/d' infile | wc -l
Obviously, you can also replace infile with a list of files.
The solution is very simple, no any magic.
use strict;
use warnings;
use feature 'say';
my $count = 0;
while( <> ) {
$count++ unless /^\s*$|^\s*#/;
}
say "Total $count lines";
Reference:
<>

Perl-Copying file from one location to other but content not copying

I am writing a script in perl where I am creating a file and getting input from user for file but when I am copying that file to other location the file is copying but it is empty only. My code is
# !/usr/bin/perl -w
for($i = 1;$i<5;$i++)
{
open(file1,"</u/man/fr$i.txt");
print "Enter text for file $i";
$txt = <STDIN>;
print file1 $txt;
open(file2,">/u/man/result/fr$i.txt");
while(<file1>)
{
print file2 $_;
}
close(file1);
close(file2);
}
fr1 to fr4 are creating but these are empty. like when I run my code it is asking for input i provide the input and code run without error but still the files are empty. Please help.
in line number 4 I changed < to > also as I thought for creating new file it might need that but still it is not working
You need to close the filehandle that was written to in order to be able to read from that file.
use warnings;
use strict;
use feature 'say';
for my $i (1..4)
{
my $file = "file_$i.txt";
open my $fh, '>', $file or die "Can't open $file: $!";
say $fh "Written to $file";
# Opening the same filehandle first *closes* it if already open
open $fh, '<', $file or die "Can't open $file: $!";
my $copy = "copy_$i.txt";
open my $fh_cp, '>', $copy or die "Can't open $copy: $!";
while (<$fh>) {
print $fh_cp $_;
}
close $fh_cp; # in case of early errors in later iterations
close $fh;
}
This creates the four files, file_1.txt etc, and their copies, copy_1.txt etc.
Please note the compulsory checking whether open worked.
You can't write to a filehandle that's not open for writing. You can't read from a filehandle that's not open for reading. Never ignore the return value of open.
# !/usr/bin/perl
use warnings; # Be warned about mistakes.
use strict; # Prohibit stupid things.
for my $i (1 .. 4) { # lexical variable, range
open my $FH1, '>', "/u/man/fr$i.txt" # 3 argument open, lexical filehandle, open for writing
or die "$i: $!"; # Checking the return value of open
print "Enter text for file $i: ";
my $txt = <STDIN>;
print {$FH1} $txt;
open my $FH2, '<', "/u/man/fr$i.txt" # Reopen for reading.
or die "$i: $!";
open my $FH3, '>', "/u/man/result/fr$i.txt" or die "$i: $!";
while (<$FH2>) {
print {$FH3} $_;
}
close $FH3;
}
I opened the file in write mode using filehandler1 Then i again opened the file in read mode using same filehandler1 then I opened filehandler2 for destiantion So it is working fine for me then.
system("cp myfile1.txt /somedir/myfile2.txt")
`cp myfile1.txt /somedir/myfile2.txt`

Writing results in a text file with perl

I have a problem when the script print the whole line of text file in a result text file:
use strict;
use warnings;
use autodie;
my $out = "result2.txt";
open my $outFile, ">$out" or die $!;
my %permitted = do {
open my $fh, '<', 'f1.txt';
map { /(.+?)\s+\(/, 1 } <$fh>;
};
open my $fh, '<', 'f2.txt';
while (<$fh>) {
my ($phrase) = /(.+?)\s+->/;
if ($permitted{$phrase}) {
print $outFile $fh;
}
close $outFile;
The problem is in this line
print $outFile $fh;
Any idea please?
Thank you
print $outFile $fh is printing the value of the file handle $fh to the file handle $outFile. Instead you want to print the entire current line, which is in $_.
There are a couple of other improvements that can be made
You should always use the three-parameter form of open, so the open mode appears on its own as the second paremeter
There is no need to test the success of an open of autodie is in place
If you have a variable that contains the name of the output file, then you really should have ones for the names of the two input files as well
This is how your program should look. I hope it helps.
use strict;
use warnings;
use autodie;
my ($in1, $in2, $out) = qw/ f1.txt f2.txt result2.txt /;
my %permitted = do {
open my $fh, '<', $in1;
map { /(.+?)\s+\(/, 1 } <$fh>;
};
open my $fh, '<', $in2;
open my $outfh, '>', $out;
while (<$fh>) {
my ($phrase) = /(.+?)\s+->/;
if ($permitted{$phrase}) {
print $outfh $_;
}
}
close $outfh;
I think you want print $outfile $phrase here, don't you? The line you currently have is trying to print out a file handle reference ($fh) to a file ($outfile).
Also, just as part of perl best practices, you'll want to use the three argument open for your first open line:
open my $outFile, ">", $out or die $!;
(FWIW, you're already using 3-arg open for your other two calls to open.)
Although Borodin has provided an excellent solution to your question, here's another option where you pass your 'in' files' names to the script on the command line, and let Perl handle the opening and closing of those files:
use strict;
use warnings;
my $file2 = pop;
my %permitted = map { /(.+?)\s+\(/, 1 } <>;
push #ARGV, $file2;
while (<>) {
my ($phrase) = /(.+?)\s+->/;
print if $permitted{$phrase};
}
Usage: perl script.pl inFile1 inFile2 [>outFile]
The last, optional parameter directs output to a file.
The pop command implicitly removes inFile2's name off of #ARGV, and stores it in $file2. Then, inFile1 is read using the <> directive. The file name of inFile2 is then pushed onto #ARGV, and that file is read and a line is printed if $permitted{$phrase} is true.
Running the script without the last, optional parameter will print results (if any) to the screen. Using the last parameter saves output to a file.
Hope this helps!

How to Write a Variable to File in Perl

I have info contained in a variable that I need to have written to a file. My script needs to be create the file and then write to it.
Here's my current script:
my $file_location = '/network/$custom_directory/$custom_filename';
open(my $file ">", $file_location) or die $!;
print $file "$variable_data";
close $file;
I'm getting the feeling that my script is getting hung up on the actual file creation, rather than the variable-writing process.
The error I get when I run the script is: 'No such file or directory' at the line where I try to open the file.
You have a syntax error in your programme. All three arguments of open must be separated by commas.
open my $file, '>', $file_location or die $!;
Single quotes do not interpolate, unlike double quotes, so you probably need them in the file path:
my $file_location = "/network/$custom_directory/$custom_filename";
BTW: Including a sole variable into double quotes server no purpose for string contents. You can equivalently
print $file $variable_data;
You didn’t say what your error is.
But you’re missing a comma.
You also have the wrong quotes.
You also (probably) forgot the newline at the end.
And you forgot to check that the close succeeded lest your filesystem should have filled up.
You may have forgotten the binmode or the encoding.
Which gives you something like this, with obligatory preamble:
#!/usr/bin/perl
use strict;
use warnings;
my $custom_directory = "something old";
my $custom_filename = "something new";
my $data = "something borrowed";
my $path = "/network/$custom_directory/$custom_filename";
open(my $handle, ">", $path) || die "can't open $path: $!";
binmode($handle); # for raw; else set the encoding
print $handle "$data\n";
close($handle) || die "can't close $path: $!";
Two things: First the file location is in single-quotes, so the $ variables won't be interpolated. Second, you're missing a comma in the call to open. The code should read:
my $file_location = "/network/$custom_directory/$custom_filename";
open(my $file, ">", $file_location) or die $!;
First,
use strict;
use warnings;
may help. Second, variable interpolation requires double quoted strings:
my $file_location = "/network/$custom_directory/$custom_filename";
Third, you may probably need a \n at the print statement:
print $file "$variable_data\n";
And finally, your open statement should be:
open my $file, ">", $file_location or die $!;

How to refer to perl arguments when looping through them?

Hi I'm trying to use the code written in this answer https://stackoverflow.com/a/1712480/1740992 :
foreach (#ARGV){
print "file: $_\n";
# open your file here...
#..do something
# close your file
}
I don't know how to refer to the argument. When my scipt was just running on one file I open it by running:
$kml = "adair.kml";
open INPUT, "<$kml";
what do I replace my filename with?
I've tried $ARGV[n]
thanks
You're already using it, it is $_.
You can use a named variable instead with:
foreach my $foo (#ARGV){
for my $arg (#ARGV) {
open my $fh, '<', $arg or die "Cannot open '$arg': $!";
# ...
close $fh;
}
Your code block:
$kml = "adair.kml";
open INPUT, "<$kml";
should be replaced with:
use strict;
use warnings;
my $kml = $ARGV[0];
open my $input_fh, '<', $kml or die "Couldn't open $kml $!";
if your code is only going to run with one argument.