I am calling a perl program from a bash script. This Perl script works when I flag its arguments from the command line but when I call it from the bash script it gives an error.
Here is the relevant part of the bash script:
for ((i=1; i<=$z; i++))
do
if (($i%2 == 0)); then
~/Desktop/SNP_finder.pl `awk 'FNR == '$i <"${d}".fasta` `awk ...` "${d}".txt
else
...
and the Perl script:
local $, = "\t";
my #read = split('', $ARGV[0]);
my #reference = split('', $ARGV[1]);
my $filename = $ARGV[2];
...
open(my $fh, '>>', $filename) or die;
print $fh "Reference ", "SNP ", "Location\n";
...
I can run SNP_finder.pl fine from the terminal window but when I pipe it to this Bash script it gives me the error Use of unitialized value $filename and claims the value of $filename is ''.
The shell is performing word splitting. If your awk scripts return empty, the "${d}".txt parameter will end up as $ARGV[1] or $ARGV[0]. If your awk scripts return a string containing whitespace, it will end up further down...
To avoid this, you should quote them, like this:
"`awk ...`"
Even better would be:
"$(awk ...)"
Related
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 );
I am writing a script for replacing 2 words from a text file. The script is
count=1
for f in *.pdf
do
filename="$(basename $f)"
filename="${filename%.*}"
filename="${filename//_/ }"
echo $filename
echo $f
perl -pe 's/intime_mean_pu.pdf/'$f'/' fig.tex > fig_$count.tex
perl -pi 's/TitleFrame/'$filename'/' fig_$count.tex
sed -i '/Pointer-rk/r fig_'$count'.tex' $1.tex
count=$((count+1))
done
But the replacing of words using the second perl command is giving error:
Can't open perl script "s/TitleFrame/Masses1/": No such file or directory
Please suggest what I am doing wrong.
You could change your script to something like this:
#!/bin/bash
for f in *.pdf; do
filename=$(basename "$f" .pdf)
filename=${filename//_/}
perl -spe 's/intime_mean_pu.pdf/$a/;
s/TitleFrame/$b/' < fig.tex -- -a="$f" -b="$filename" > "fig_$count.tex"
sed -i "/Pointer-rk/r fig_$count.tex" "$1.tex"
((++count))
done
As well as some other minor changes to your script, I have made use of the -s switch to Perl, which means that you can pass arguments to the one-liner. The bash variables have been double quoted to avoid problems with spaces in filenames, etc.
Alternatively, you could do the whole thing in Perl:
#!/usr/bin/env perl
use strict;
use warnings;
use autodie;
use File::Basename;
my $file_arg = shift;
my $count = 1;
for my $f (glob "*.pdf") {
my $name = fileparse($f, qq(.pdf));
open my $in, "<", $file_arg;
open my $out, ">", 'tmp';
open my $fig, "<", 'fig.tex';
# copy up to match
while (<$in>) {
print $out $_;
last if /Pointer-rk/;
}
# insert contents of figure (with substitutions)
while (<$fig>) {
s/intime_mean_pu.pdf/$f/;
s/TitleFrame/$name/;
print $out $_;
}
# copy rest of file
print $out $_ while <$in>;
rename 'tmp', $file_arg;
++$count;
}
Use the script like perl script.pl "$1.tex".
You're missing the -e in the second perl call
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.
I am in school and in a network programming class. I have no Perl experience at all. Our assignment is as follows.
Write a short program that uses the backticks `` function to run the external command ps -aux to list all currently running processes owned by the user, whose username is given as a command line argument.
Hint:
$ARGV[0] or die "without a username given\n";
my $.... = $ARGV[0];
my #ps = `ps -axu`;
foreach my $..... (#ps) {
print $line if …../;
}
I have changed the code to this:
#!/usr/local/bin/perl -w
use strict;
use warnings;
my $line;
$ARGV[0] or die "without a username given\n";
my $test = $ARGV[0];
my #ps = `ps -axu`;
foreach my $test (#ps) {
print $line if ...../;
}
however, i keep getting this error:
syntax error at C:\Perl\W4A2new.pl line 10, near "if ..."
Search pattern not terminated at C:\Perl\W4A2new.pl line 10.
Can anyone help me out??? Please. thank you.
The ps command is usually invoked like ps aux (without leading minus).
Specifying a user name in the ps argument has no effect I can see
Using backticks is convenient, but could be considered bad style.
The first column of the ps aux output can contain a numeric user ID.
We can use /etc/passwd to translate between user IDs and user names.
Here is code that prints out all ps lines that start with a given string:
#!/usr/bin/perl
use strict; use warnings;
print grep /^\Q$ARGV[0]\E/, `ps aux`;
That last line could be written verbose:
my #temp;
for (`ps aux`) {
push #temp, $_ if /^\Q$ARGV[0]\E/;
}
print #temp;
That script would be invoked like perl script.pl root or perl script.pl 1000.
Now as I said, we can translate between usernames and user IDs. Also, we can treat the ps output as the column data it is. The columns are
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
We can access these cols by my #cols = split ' ', $line, 11. We have to specify a maximum number of cols as the command may contain whitespaces. If the user contains only digit characters, we translate to the corresponding name in /etc/passwd:
#!/usr/bin/perl
use strict; use warnings;
my $username = $ARGV[0] or die <<"END_USAGE";
USAGE: perl $0 USERNAME
USERNAME -- the full username you want ps output for.
END_USAGE
my %id_2_name = do {
open my $passwd, "<", "/etc/passwd" or die "Can't open passwords: $!";
map { (split /:/)[2,0] } <$passwd>;
};
open my $ps, "-|", "ps", "aux" or die "Can't open ps command: $!";
while(<$ps>) {
my #cols = split ' ', $_, 11;
$cols[0] = $id_2_name{$cols[0]} unless $cols[0] =~ /[^0-9]/;
print join " ", #cols if $cols[0] eq $username;
}
#!/usr/local/bin/perl -w
use strict;
use warnings;
my $username = $ARGV[0] or die "without a username given\n";
my #ps = `ps -axu $username`;
foreach my $line (#ps) {
print $line, "\n";
}
you must execute this script in this way (assuming the script name is myscript.pl):
perl myscript.pl your_operating_system_user_name
I have the following script that takes in an input file, output file and
replaces the string in the input file with some other string and writes out
the output file.
I want to change the script to traverse through a directory of files
i.e. instead of prompting for input and output files, the script should take
as argument a directory path such as C:\temp\allFilesTobeReplaced\ and
search for a string x and replace it with y for all files under that
directory path and write out the same files.
How do I do this?
Thanks.
$file=$ARGV[0];
open(INFO,$file);
#lines=<INFO>;
print #lines;
open(INFO,">c:/filelist.txt");
foreach $file (#lines){
#print "$file\n";
print INFO "$file";
}
#print "Input file name: ";
#chomp($infilename = <STDIN>);
if ($ARGV[0]){
$file= $ARGV[0]
}
print "Output file name: ";
chomp($outfilename = <STDIN>);
print "Search string: ";
chomp($search = <STDIN>);
print "Replacement string: ";
chomp($replace = <STDIN>);
open(INFO,$file);
#lines=<INFO>;
open(OUT,">$outfilename") || die "cannot create $outfilename: $!";
foreach $file (#lines){
# read a line from file IN into $_
s/$search/$replace/g; # change the lines
print OUT $_; # print that line to file OUT
}
close(IN);
close(OUT);
The use of the perl single liner
perl -pi -e 's/original string/new string/' filename
can be combined with File::Find, to give the following single script (this is a template I use for many such operations).
use File::Find;
# search for files down a directory hierarchy ('.' taken for this example)
find(\&wanted, ".");
sub wanted
{
if (-f $_)
{
# for the files we are interested in call edit_file().
edit_file($_);
}
}
sub edit_file
{
my ($filename) = #_;
# you can re-create the one-liner above by localizing #ARGV as the list of
# files the <> will process, and localizing $^I as the name of the backup file.
local (#ARGV) = ($filename);
local($^I) = '.bak';
while (<>)
{
s/original string/new string/g;
}
continue
{
print;
}
}
You can do this with the -i param:
Just process all the files as normal, but include -i.bak:
#!/usr/bin/perl -i.bak
while ( <> ) {
s/before/after/;
print;
}
This should process each file, and rename the original to original.bak And of course you can do it as a one-liner as mentioned by #Jamie Cook
Try this
#!/usr/bin/perl -w
#files = <*>;
foreach $file (#files) {
print $file . '\n';
}
Take also a look to glob in Perl:
http://perldoc.perl.org/File/Glob.html
http://www.lyingonthecovers.net/?p=312
I know you can use a simple Perl one-liner from the command line, where filename can be a single filename or a list of filenames. You could probably combine this with bgy's answer to get the desired effect:
perl -pi -e 's/original string/new string/' filename
And I know it's trite but this sounds a lot like sed, if you can use gnu tools:
for i in `find ./allFilesTobeReplaced`; do sed -i s/original string/new string/g $i; done
perl -pi -e 's#OLD#NEW#g' filename.
You can replace filename with the pattern that suits your file list.