Perl ARGV value in scalar context - perl

Given the following Perl script:
# USAGE: ./flurp -x -vf file1 file2 file3 file4
# e.
$a = shift;
$b = shift;
$c = shift;
#d = <>;
# ei. value of $b = -vf
# eii. value of #d = content of file2, file3, file4
print "$b\n";
print "#d\n";
print "$ARGV\n";
This is the output:
-vf
{contents of file2, file3, file4}
file4
I am puzzled by the output of print "$ARGV\n". If I try to do print "$ARGV[-1]\n", an empty line is printed out to STDOUT. If I directly reference $ARG[2], I get an empty line as well.
Why is the script printing file4 when $ARGV is used?
As a counter-example, I tried print "$d\n", expecting to get the last line of file4. Instead of the expected output I got an empty line. How does $ARGV work?

In answer to your specific question: "How does $ARGV work?"
$ARGV Contains the name of the current file when reading from <> .
from Variables related to filehandles in the Perl docs.
Although at the point you print $ARGV you've finished reading from file4, the variable still holds the name of the file.

Related

awk output to variable and change directory

In the below script. am not able to change the directory.i need the output like above 70% disk inside that directory which one is consuming more space.
#!/usr/bin/perl
use strict;
use warnings;
my $test=qx("df -h |awk \+\$5>=70 {print \$6} ");
chdir($test) or die "$!";
print $test;
system("du -sh * | grep 'G'");
No need to call awk in your case because Perl is quite good at splitting and printing certain lines itself. Your code has some issues:
The code qx("df -h |awk \+\$5>=70 {print \$6} ") tries to execute the string "df -h | awk ..." as a command which fails because there is no such command called "df -h | awk". When I run that code I get sh: 1: df -h |awk +>=70 {print } : not found. You can fix that by dropping the quotes " because qx() already is quoting. The variable $test is empty afterwards, so the chdir changes to your $HOME directory.
Then you'll see the next error: awk: line 1: syntax error at or near end of line, because it calls awk +\$5>=70 {print \$6}. Correct would be awk '+\$5>=70 {print \$6}', i.e. with ticks ' around the awk scriptlet.
As stated in a comment, df -h splits long lines into two lines. Example:
Filesystem 1K-blocks Used Available Use% Mounted on
/long/and/possibly/remote/file/system
10735331328 10597534720 137796608 99% /local/directory
Use df -hP to get guaranteed column order and one line output.
The last system call shows the directory usage (space) for all lines containing the letter G. I reckon that's not exactly what you want.
I suggest the following Perl script:
#!/usr/bin/env perl
use strict;
use warnings;
foreach my $line ( qx(df -hP) ) {
my ($fs, $size, $used, $avail, $use, $target) = split(/\s+/, $line);
next unless ($use =~ /^\d+\s*\%$/); # skip header line
# now $use is e.g. '90%' and we drop the '%' sign:
$use =~ s/\%$//;
if ($use > 70) {
print "almost full: $target; top 5 directories:\n";
# no need to chdir here. Simply use $target/* as search pattern,
# reverse-sort by "human readable" numbers, and show the top 5:
system("du -hs $target/* 2>/dev/null | sort -hr | head -5");
print "\n\n";
}
}
#!/usr/bin/perl
use strict;
use warnings;
my #bigd = map { my #f = split " "; $f[5] }
grep { my #f = split " "; $f[4] =~ /^(\d+)/ && $1 >= 70}
split "\n", `df -hP`;
print "big directories: $_\n" for #bigd;
for my $bigd (#bigd) {
chdir($bigd);
my #bigsubd = grep { my #f = split " "; $f[0] =~ /G/ }
split "\n", `du -sh *`;
print "big subdirectories in $bigd:\n";
print "$_\n" for #bigsubd;
}
I belive you wanted to do something like this.

Perl script: validity of a directory always return false

I want to read some parameters from a file using perl script.
I used grep command to find the values of thoses parameters.
#!/usr/bin/perl
if( scalar #ARGV ==0)
{
die "Number of argument is zero. Please use: perl <script_name> <cdc68.ini> \n";
}
if( !-f $ARGV[0]) ## if the 1st arg is not a file
{
die "$ARGV[0] is not a valid file type \nInput Arguments not correct!!! \n";
}
my $file_cnf=$ARGV[0];
my $DEST_PATH=`grep relogin_logs $file_cnf | cut -d "=" -f2`;
my $SRC_PATH=`grep dump_logs $file_cnf | cut -d "=" -f2`;
my $FINAL_LOG=`grep final_log $file_cnf | cut -d "=" -f2`;
print "\n$DEST_PATH \n $SRC_PATH \n $FINAL_LOG\n";
if ( !-d $DEST_PATH)
{
die "$DEST_PATH is not a dir";
}
else
{
print "ok";
}
the file I want to read is
cat cdc68.ini
reconn_interval=15
relogin_logs=/osp/local/home/linus/pravej/perlD/temp/relogin/
dump_logs=/osp/local/home/linus/pravej/perlD/temp/
final_log=/osp/local/home/linus/pravej/perlD/final_log/
Number_days=11
Sample output:
perl readconfig.pl cdc68.ini
/osp/local/home/linus/pravej/perlD/temp/relogin/
/osp/local/home/linus/pravej/perlD/temp/
/osp/local/home/linus/pravej/perlD/temp/relogin/
is not a dir at readconfig.pl line 26.
Can anyone suggest what I'm doing wrong here?
Please note that I dont want to use any perl module like config or tiny.pm.
Also these dir already exist in my unix system
Thanks in advance for your help
You can use perl parsing instead of shell utils
my $file_cnf = $ARGV[0];
open my $fh, "<", $file_cnf or die $!;
my %ini = map /(.+?)=(.+)/, <$fh>;
close $fh;
print "\n$ini{relogin_logs} \n $ini{dump_logs} \n $ini{final_log}\n";
mpapec's alternative parsing is much better than your current script. But, for what it's worth, your original problem is that the result of the backtick operator includes the newline character at the end of the string.
You can remove it with chomp, eg:
my $DEST_PATH=`grep relogin_logs $file_cnf | cut -d "=" -f2`;
chomp $DESTDIR;
Do this with the result of all your backtick commands.

Does "print $ARGV" alter the argument array in any way?

Here is the example:
$a = shift;
$b = shift;
push(#ARGV,$b);
$c = <>;
print "\$b: $b\n";
print "\$c: $c\n";
print "\$ARGV: $ARGV\n";
print "\#ARGV: #ARGV\n";
And the output:
$b: file1
$c: dir3
$ARGV: file2
#ARGV: file3 file1
I don't understand what exactly is happening when printing $ARGV without any index. Does it print the first argument and then remove it from the array? Because I thought after all the statements the array becomes:
file2 file3 file1
Invocation:
perl port.pl -axt file1 file2 file3
file1 contains the lines:
dir1
dir2
file2:
dir3
dir4
dir5
file3:
dir6
dir7
Greg has quoted the appropriate documentation, so here's a quick rundown of what happens
$a = shift; # "-axt" is removed from #ARGV and assigned to $a
$b = shift; # "file1" likewise
push(#ARGV,$b); # "file1" inserted at end of #ARGV
$c = <>; # "file2" is removed from #ARGV, and its file
# handle opened, the first line of file2 is read
When the file handle for "file2" is opened, it sets the file name in $ARGV. As Greg mentioned, #ARGV and $ARGV are completely different variables.
The internal workings of the diamond operator <> is probably what is confusing you here, in that it does an approximate $ARGV = shift #ARGV
In Perl, $ARGV and #ARGV are completely different. From perlvar:
$ARGV
Contains the name of the current file when reading from <>.
#ARGV
The array #ARGV contains the command-line arguments intended for the script. $#ARGV is generally the number of arguments minus one, because $ARGV[0] is the first argument, not the program's command name itself. See $0 for the command name.
No, but <> does. <> is short for <ARGV> (which in turn is short for readline(ARGV))
, where ARGV is a special file handle that reads from the files listed in #ARGV (or STDIN if #ARGV is empty). As it opens the files in #ARGV, it removes them from #ARGV and stores them in $ARGV.

Grep to match all lines of patternfile (perl -e ok too)

I'm looking for a simple/elegant way to grep a file such that every returned line must match every line of a pattern file.
With input file
acb
bc
ca
bac
And pattern file
a
b
c
The command should return
acb
bac
I tried to do this with grep -f but that returns if it matches a single pattern in the file (and not all). I also tried something with a recursive call to perl -ne (foreach line of the pattern file, call perl -ne on the search file and try to grep in place) but I couldn't get the syntax parser to accept a call to perl from perl, so not sure if that's possible.
I thought there's probably a more elegant way to do this, so I thought I'd check. Thanks!
===UPDATE===
Thanks for your answers so far, sorry if I wasn't clear but I was hoping for just a one-line result (creating a script for this seems too heavy, just wanted something quick). I've been thinking about it some more and I came up with this so far:
perl -n -e 'chomp($_); print " | grep $_ "' pattern | xargs echo "cat input"
which prints
cat input | grep a | grep b | grep c
This string is what I want to execute, I just need to somehow execute it now. I tried an additional pipe to eval
perl -n -e 'chomp($_); print " | grep $_ "' pattern | xargs echo "cat input" | eval
Though that gives the message:
xargs: echo: terminated by signal 13
I'm not sure what that means?
One way using perl:
Content of input:
acb
bc
ca
bac
Content of pattern:
a
b
c
Content of script.pl:
use warnings;
use strict;
## Check arguments.
die qq[Usage: perl $0 <input-file> <pattern-file>\n] unless #ARGV == 2;
## Open files.
open my $pattern_fh, qq[<], pop #ARGV or die qq[ERROR: Cannot open pattern file: $!\n];
open my $input_fh, qq[<], pop #ARGV or die qq[ERROR: Cannot open input file: $!\n];
## Variable to save the regular expression.
my $str;
## Read patterns to match, and create a regex, with each string in a positive
## look-ahead.
while ( <$pattern_fh> ) {
chomp;
$str .= qq[(?=.*$_)];
}
my $regex = qr/$str/;
## Read each line of data and test if the regex matches.
while ( <$input_fh> ) {
chomp;
printf qq[%s\n], $_ if m/$regex/o;
}
Run it like:
perl script.pl input pattern
With following output:
acb
bac
Using Perl, I suggest you read all the patterns into an array and compile them. Then you can read through your input file using grep to make sure all of the regexes match.
The code looks like this
use strict;
use warnings;
open my $ptn, '<', 'pattern.txt' or die $!;
my #patterns = map { chomp(my $re = $_); qr/$re/; } grep /\S/, <$ptn>;
open my $in, '<', 'input.txt' or die $!;
while (my $line = <$in>) {
print $line unless grep { $line !~ $_ } #patterns;
}
output
acb
bac
Another way is to read all the input lines and then start filtering by each pattern:
#!/usr/bin/perl
use strict;
use warnings;
open my $in, '<', 'input.txt' or die $!;
my #matches = <$in>;
close $in;
open my $ptn, '<', 'pattern.txt' or die $!;
for my $pattern (<$ptn>) {
chomp($pattern);
#matches = grep(/$pattern/, #matches);
}
close $ptn;
print #matches;
output
acb
bac
Not grep and not a one liner...
MFILE=file.txt
PFILE=patterns
i=0
while read line; do
let i++
pattern=$(head -$i $PFILE | tail -1)
if [[ $line =~ $pattern ]]; then
echo $line
fi
# (or use sed instead of bash regex:
# echo $line | sed -n "/$pattern/p"
done < $MFILE
A bash(Linux) based solution
#!/bin/sh
INPUTFILE=input.txt #Your input file
PATTERNFILE=patterns.txt # file with patterns
# replace new line with '|' using awk
PATTERN=`awk 'NR==1{x=$0;next}NF{x=x"|"$0}END{print x}' "$PATTERNFILE"`
PATTERNCOUNT=`wc -l <"$PATTERNFILE"`
# build regex of style :(a|b|c){3,}
PATTERN="($PATTERN){$PATTERNCOUNT,}"
egrep "${PATTERN}" "${INPUTFILE}"
Here's a grep-only solution:
#!/bin/sh
foo ()
{
FIRST=1
cat pattern.txt | while read line; do
if [ $FIRST -eq 1 ]; then
FIRST=0
echo -n "grep \"$line\""
else
echo -n "$STRING | grep \"$line\""
fi
done
}
STRING=`foo`
eval "cat input.txt | $STRING"

How can I do bulk search and replace with Perl?

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.