Giving both input & output file at command line - perl

Si I have this line in the perl script which prints the output to the STDOUT/console
printf "Line no. $i"
What code shall I include in the program to direct this output to an output file given by user at the command line itself (as undermentioned)
Right now ,the following portion asks the user for input file:
print "enter file name";
chomp(my $file=<STDIN>);
open(DATA,$file) or die "error reading";
But I dont want to ask the user for either of input/output file.
What I want is a way in which user could give in the input as well as output file from command line while running the program.
perl input_file output_file program.pl
What code shall i just include for this.

You can use shift to read the command line arguments to your script. shift reads and removes the first element of an array. If no array is specified (and not inside a subroutine), it will implicitly read from #ARGV, which contains the list of arguments passed to your script. For example:
use strict;
use warnings;
use autodie;
# check that two arguments have been passed
die "usage: $0 input output\n" unless #ARGV == 2;
my $infile = shift;
my $outfile = shift;
# good idea to sanitise the arguments here
open my $in, "<", $infile;
open my $out, ">", $outfile;
while (<$in>) {
print $out $_;
}
close $in;
close $out;
You could call this script like perl script.pl input_file output_file and it would copy the contents of input_file to output_file.

The easiest approach here is to ignore input and output files within your program. Just read from STDIN and write to STDOUT. Let the user redirect those filehandles when calling your program.
Your program looks something like this:
#!/usr/bin/perl
use strict;
use warnings;
while (<STDIN>) {
# do something useful to the data in $_
print;
}
And you call it like this:
$ ./your_program.pl inputfile.txt > outputfile.txt
This is known as the "Unix Filter Model" and it's the most flexible way to write programs that read input and produce output.

You can use #ARGV variable ,
use strict ;
use warnings ;
if ( #ARGV != 2 )
{
print "Usage : <program.pl> <input> <output>\n" ;
exit ;
}
open my $Input,$ARGV[0] or die "error:$!\n" ;
open my $Output,">>" .$ARGV[1] or die "error:$!\n";
print $Output $_ while (<$Input> ) ;
close ($Input) ;
close ($Output) ;
Note:
You should run the program perl program.pl input_file output_file this format.

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 );

foreach and special variable $_ not behaving as expected

I'm learning Perl and wrote a small script to open perl files and remove the comments
# Will remove this comment
my $name = ""; # Will not remove this comment
#!/usr/bin/perl -w <- wont remove this special comment
The name of files to be edited are passed as arguments via terminal
die "You need to a give atleast one file-name as an arguement\n" unless (#ARGV);
foreach (#ARGV) {
$^I = "";
(-w && open FILE, $_) || die "Oops: $!";
/^\s*#[^!]/ || print while(<>);
close FILE;
print "Done! Please see file: $_\n";
}
Now when I ran it via Terminal:
perl removeComments file1.pl file2.pl file3.pl
I got the output:
Done! Please see file:
This script is working EXACTLY as I'm expecting but
Issue 1 : Why $_ didn't print the name of the file?
Issue 2 : Since the loop runs for 3 times, why Done! Please see file: was printed only once?
How you would write this script in as few lines as possible?
Please comment on my code as well, if you have time.
Thank you.
The while stores the lines read by the diamond operator <> into $_, so you're writing over the variable that stores the file name.
On the other hand, you open the file with open but don't actually use the handle to read; it uses the empty diamond operator instead. The empty diamond operator makes an implicit loop over files in #ARGV, removing file names as it goes, so the foreach runs only once.
To fix the second issue you could use while(<FILE>), or rewrite the loop to take advantage of the implicit loop in <> and write the entire program as:
$^I = "";
/^\s*#[^!]/ || print while(<>);
Here's a more readable approach.
#!/usr/bin/perl
# always!!
use warnings;
use strict;
use autodie;
use File::Copy;
# die with some usage message
die "usage: $0 [ files ]\n" if #ARGV < 1;
for my $filename (#ARGV) {
# create tmp file name that we are going to write to
my $new_filename = "$filename\.new";
# open $filename for reading and $new_filename for writing
open my $fh, "<", $filename;
open my $new_fh, ">", $new_filename;
# Iterate over each line in the original file: $filename,
# if our regex matches, we bail out. Otherwise we print the line to
# our temporary file.
while(my $line = <$fh>) {
next if $line =~ /^\s*#[^!]/;
print $new_fh $line;
}
close $fh;
close $new_fh;
# use File::Copy's move function to rename our files.
move($filename, "$filename\.bak");
move($new_filename, $filename);
print "Done! Please see file: $filename\n";
}
Sample output:
$ ./test.pl a.pl b.pl
Done! Please see file: a.pl
Done! Please see file: b.pl
$ cat a.pl
#!/usr/bin/perl
print "I don't do much\n"; # comments dont' belong here anyways
exit;
print "errrrrr";
$ cat a.pl.bak
#!/usr/bin/perl
# this doesn't do much
print "I don't do much\n"; # comments dont' belong here anyways
exit;
print "errrrrr";
Its not safe to use multiple loops and try to get the right $_. The while Loop is killing your $_. Try to give your files specific names inside that loop. You can do this with so:
foreach my $filename(#ARGV) {
$^I = "";
(-w && open my $FILE,'<', $filename) || die "Oops: $!";
/^\s*#[^!]/ || print while(<$FILE>);
close FILE;
print "Done! Please see file: $filename\n";
}
or that way:
foreach (#ARGV) {
my $filename = $_;
$^I = "";
(-w && open my $FILE,'<', $filename) || die "Oops: $!";
/^\s*#[^!]/ || print while(<$FILE>);
close FILE;
print "Done! Please see file: $filename\n";
}
Please never use barewords for filehandles and do use a 3-argument open.
open my $FILE, '<', $filename — good
open FILE $filename — bad
Simpler solution: Don't use $_.
When Perl was first written, it was conceived as a replacement for Awk and shell, and Perl heavily borrowed from that syntax. Perl also for readability created the special variable $_ which allowed you to use various commands without having to create variables:
while ( <INPUT> ) {
next if /foo/;
print OUTPUT;
}
The problem is that if everything is using $_, then everything will effact $_ in many unpleasant side effects.
Now, Perl is a much more sophisticated language, and has things like locally scoped variables (hint: You don't use local to create these variables -- that merely gives _package variables (aka global variables) a local value.)
Since you're learning Perl, you might as well learn Perl correctly. The problem is that there are too many books that are still based on Perl 3.x. Find a book or web page that incorporates modern practice.
In your program, $_ switches from the file name to the line in the file and back to the next file. It's what's confusing you. If you used named variables, you could distinguished between files and lines.
I've rewritten your program using more modern syntax, but your same logic:
use strict;
use warnings;
use autodie;
use feature qw(say);
if ( not $ARGV[0] ) {
die "You need to give at least one file name as an argument\n";
}
for my $file ( #ARGV ) {
# Remove suffix and copy file over
if ( $file =~ /\..+?$/ ) {
die qq(File "$file" doesn't have a suffix);
}
my ( $output_file = $file ) =~ s/\..+?$/./; #Remove suffix for output
open my $input_fh, "<", $file;
open my $output_fh, ">", $output_file;
while ( my $line = <$input_fh> ) {
print {$output_fh} $line unless /^\s*#[^!]/;
}
close $input_fh;
close $output_fh;
}
This is a bit more typing than your version of the program, but it's easier to see what's going on and maintain.

How to print all filenames in a directory while running line by line on all the files

The program I wrote goes through all the files in directory and sub directories line by line and does some commands the include $counter.
I would like to make a .txt file every line will look like this:
<file name> <$counter in the beginning of the file>
For example if I have three file a.txt , b.txt and c.txt in file a the counter counts 10 in file b it counts 20 and in file c it counts 30 the file would look like this:
a.txt 0
b.txt 10
c.txt 20
My program looks like this:
use strict;
use warnings;
use File::Find;
my $dir = "C:/New Folder";
open (MYFILE, '>>data.txt');
# fill up our argument list with file names:
find(sub { if (-f && /\.[c]$/) { push #ARGV, $File::Find::name } }, $dir);
$^I = ".bak"; # supply backup string to enable in-place edit
foreach $argvv(#ARGV)
{
while (<>)
{
if ($prev_arg ne $argvv)
{
print MYFILE "$argvv $counter\n";
$prev_arg = $argvv;
}
#some unrelated line by line code here
close (MYFILE);
}
}
What I was trying to do is to make the program print the file name and the counter every time it finishes going through a file and starts another one.
The data.txt file i get is the name of the first file and the counter printed for each line of each file in the directory.
Needless to say that I'm a total noob in Perl so I would really appreciate some help.
Thanks :)
There are a few issues with your code.
You are using strict and warnings but you have not declared $prev_arg and $argvv. The warnings have told you about them.
You never open a file for reading. Instead, you try to use the -i command line switch and an argument list to read from STDIN. None of that makes sense to me. Instead, you should be using a normal array and open the files one by one.
You are using old-fashioned bareword filehandles and the two-argument form of open. Instead, please use lexical filehandles as they are not global but only exist in their surrounding block. Use the three-argument form of open so you don't run into security problems. See here.
Try this:
use warnings;
use strict;
use File::Find;
my $dir = 'D:/temp';
my #files;
find(
sub {
if (-f && /\.c.txt$/) {
push #files, $File::Find::name }
},
$dir
);
open my $fh_out, '>>', 'data.txt' # open the output filehandle
or die $!;
foreach my $file (#files) { # iterate the file list
open my $fh_in, '<', $file # open the current file for reading
or die $!;
my $counter = <$fh_in>; # read the first line
chomp $counter; # remove trailing linebreak
close $fh_in;
print $fh_out "$file $counter\n"; # close the input filehandle (explicit)
}
close $fh_out; # close the output filehandle

No such file or directory at ./script line 17

Exact error:
$ ./script.pl file.txt
Can't open file.txt: No such file or directory at ./script.pl line 17.
Use of uninitialized value in chomp at ./script.pl line 17.
Username: Password:
I'm writing a script that takes a filename from the commandline and then writes its output to it:
#!/usr/bin/perl
use warnings;
use strict;
use Term::ReadKey;
my #array;
my $user;
my $pass;
# get login info
print "Username: ";
chomp($user = <>); # line 17
print "Password: ";
ReadMode 2;
chomp($pass = <>);
ReadMode 0;
print " \n";
# ...
# connect to database, and save the info in "#array"
# ...
# save the array to a file
if (defined($ARGV[0])) {
open (MYFILE, ">".$ARGV[0]) or die "Can't open ".$ARGV[0].": $!\n";
foreach (#array) {
print MYFILE $_."\n";
}
close (MYFILE);
# otherwise, print the names to the screen
} else {
foreach (#array) {
print $_."\n";
}
}
However if I replace ARGV[0] with "file.txt" or something similar, printing to the file works fine. If I do not provide a filename, the script works fine. My hunch is that the print statement is interfering with the iostream buffer but I can't figure out how to fix it.
That is how the magic diamond operator works in Perl. If you start the script with an argument, it tries to read input from the file. If you feed it a standard input, it reads from there.
Use <STDIN>, not <>, to read from standard input if you are planning to use #ARGV.
Or, even better, read directly from terminal (if STDIN is a terminal). A quick search brought up Term::ReadKey, but I haven't tried it myself.

Programmatically read from STDIN or input file in Perl

What is the slickest way to programatically read from stdin or an input file (if provided) in Perl?
while (<>) {
print;
}
will read either from a file specified on the command line or from stdin if no file is given
If you are required this loop construction in command line, then you may use -n option:
$ perl -ne 'print;'
Here you just put code between {} from first example into '' in second
This provides a named variable to work with:
foreach my $line ( <STDIN> ) {
chomp( $line );
print "$line\n";
}
To read a file, pipe it in like this:
program.pl < inputfile
The "slickest" way in certain situations is to take advantage of the -n switch. It implicitly wraps your code with a while(<>) loop and handles the input flexibly.
In slickestWay.pl:
#!/usr/bin/perl -n
BEGIN: {
# do something once here
}
# implement logic for a single line of input
print $result;
At the command line:
chmod +x slickestWay.pl
Now, depending on your input do one of the following:
Wait for user input
./slickestWay.pl
Read from file(s) named in arguments (no redirection required)
./slickestWay.pl input.txt
./slickestWay.pl input.txt moreInput.txt
Use a pipe
someOtherScript | ./slickestWay.pl
The BEGIN block is necessary if you need to initialize some kind of object-oriented interface, such as Text::CSV or some such, which you can add to the shebang with -M.
-l and -p are also your friends.
You need to use <> operator:
while (<>) {
print $_; # or simply "print;"
}
Which can be compacted to:
print while (<>);
Arbitrary file:
open my $F, "<file.txt" or die $!;
while (<$F>) {
print $_;
}
close $F;
If there is a reason you can't use the simple solution provided by ennuikiller above, then you will have to use Typeglobs to manipulate file handles. This is way more work. This example copies from the file in $ARGV[0] to that in $ARGV[1]. It defaults to STDIN and STDOUT respectively if files are not specified.
use English;
my $in;
my $out;
if ($#ARGV >= 0){
unless (open($in, "<", $ARGV[0])){
die "could not open $ARGV[0] for reading.";
}
}
else {
$in = *STDIN;
}
if ($#ARGV >= 1){
unless (open($out, ">", $ARGV[1])){
die "could not open $ARGV[1] for writing.";
}
}
else {
$out = *STDOUT;
}
while ($_ = <$in>){
$out->print($_);
}
Do
$userinput = <STDIN>; #read stdin and put it in $userinput
chomp ($userinput); #cut the return / line feed character
if you want to read just one line
Here is how I made a script that could take either command line inputs or have a text file redirected.
if ($#ARGV < 1) {
#ARGV = ();
#ARGV = <>;
chomp(#ARGV);
}
This will reassign the contents of the file to #ARGV, from there you just process #ARGV as if someone was including command line options.
WARNING
If no file is redirected, the program will sit their idle because it is waiting for input from STDIN.
I have not figured out a way to detect if a file is being redirected in yet to eliminate the STDIN issue.
if(my $file = shift) { # if file is specified, read from that
open(my $fh, '<', $file) or die($!);
while(my $line = <$fh>) {
print $line;
}
}
else { # otherwise, read from STDIN
print while(<>);
}