How can I check if a command-line argument is equal to '<' from input in Perl? - perl

#!/usr/bin/perl -w
foreach $arg (#ARGV) {
if ($arg eq "--version") {
print "$0: version 0.1\n";
exit(0);
} elsif ($arg =~ /-[0-9]+/) {
$n = $arg;
$n =~ s/-//g;
} elsif ($arg eq <){
print "in";
} else {
push #files, $arg;
}
}
How can I check if $arg is equal to < from input?
thanks in advance.

You correctly used quotes for the string --version. You should use quotes for <, too.
Note that you are checking command line arguments, not input. Also note that on *nix systems, if you call the script like
myscript.pl -12 < input
the < input part is processed by the shell and doesn't appear in the argument list.
See also Getopt::Long.

#hahahakebab: What #choroba said is exactly correct. When you are passing < as the command line argument put that in double quote "<" so that it takes as input to the program and modified code as below
foreach my $arg (#ARGV) {
if ($arg eq "--version") {
print "$0: version 0.1\n";
exit(0);
} elsif ($arg =~ /-[0-9]+/) {
$n = $arg;
$n =~ s/-//g;
} elsif ($arg eq "<"){
print "in";
} else {
push #files, $arg;
}
}

If < was used in the command line, then standard input is coming from a file.
If < was not used in the command line (and | is not used to specify input from the output stream of another process), then standard input is from a terminal.
You can distinguish between these two cases in Perl by checking -t STDIN
if (-t STDIN) {
print "input is from terminal\n";
} else {
print "input is from a file/pipe\n";
}

Related

perl cookbook fixstyle2 perplexed by & 1

From once again perl cookbook, I know what this program does and I understand most of it but below code is escapes me.
It is using basically if else but what is ( $i++ & 1 ) mean??
#!/usr/bin/perl -w
# fixstyle2 - like fixstyle but faster for many many matches
use strict;
my $verbose = (#ARGV && $ARGV[0] eq '-v' && shift);
my %change = ();
while (<DATA>) {
chomp;
my ($in, $out) = split /\s*=>\s*/;
next unless $in && $out;
$change{$in} = $out;
}
if (#ARGV) {
$^I = ".orig";
} else {
warn "$0: Reading from stdin\n" if -t STDIN;
}
while (<>) {
my $i = 0;
s/^(\s+)// && print $1; # emit leading whitespace
for (split /(\s+)/, $_, -1) { # preserve trailing whitespace
print( ($i++ & 1) ? $_ : ($change{$_} || $_));
}
}
__END__
analysed analyzed
$i++ returns the value of $i and increments $i afterwards. & is the "bitwise and" operator, so it takes the before mentioned value of $i and checks its last bit (as 1 in binary is 00..01).
As $i is incremented by 1 in each iteration, in binary its last bit changes from 1 to 0 and vice versa in each step, therefore the expression just determines odd versus even words.

How Can I avoid a Indefinite Loop While Searching a string from a no of files

In my below program, I was trying to search a string from no of files In a folder but output Is printing in continuous manner rather than stopping after required search. Can some one pls help to point out the error ?
i.e. I am trying to Search the string "VoLTE SIPTX: [SIPTX-SIP] ==> REGISTER" from #files but I am not getting the desired output but I am getting repetitive output of my strings.
# #!/usr/bin/perl
# use strict;
use warnings;
&IMS_Compare_Message();
sub IMS_Compare_Message
{
print "Entering the value i.e. the IMS Message to compare with";
my $value = '';
my $choice = '';
my $loop = '';
print "\nThe script path & name is $0\n";
print "\nPlease enter desired number to select any of the following
(1) Start Comparing REGISTER message !!
(2) Start Comparing SUBSCRIBE message
(3) Start Comparing INVITE message \n";
$value = <STDIN>;
if ($value == 1 )
{
print "\n Start Comparing REGISTER message\n\n";
$IMS_Message = "VoLTE SIPTX: [SIPTX-SIP] ==> REGISTER";
#chomp ($IMS_Message);
}
elsif ($value == 2)
{
print "\n SUBSCRIBE message Flow\n\n";
}
elsif ($value == 3)
{
print "\n INVITE message Flow\n\n";
}
else
{
print "\nThe input is not valid!\n";
print "\nDo you want to continue selecting a Automation Mode again (Y or N)?\n";
$choice = <STDIN>;
if( $choice =~ /[Yy]/) {
test_loop();
} else {
exit;
}
}
my $kw = "$IMS_Message";
my #files = grep {-f} (<*main_log>);
foreach my $file (#files)
{
open(my $fh, '<', $file) or die $!;
my #content = <$fh>;
close($fh);
my $l = 0;
$search = chomp ($kw);
#my $search = quotemeta($kw);
foreach (#content)
{ # go through every line for this keyword
$l++;
if (/$search/)
{
printf 'Found keyword %s in file %s, line %d:%s'.$/, $kw, $file, $l, $_
}
}
}
}
After Modificaiton
# #!/usr/bin/perl
use strict;
use warnings;
print "Entering the value i.e. the IMS Message to compare with";
my $value = '';
my $choice = '';
my $loop = '';
my $IMS_Message = '';
my $search = '';
my $kw = '';
print "\nThe script path & name is $0\n";
print "\nPlease enter desired number to select any of the following
(1) Start Comparing REGISTER message !!
(2) Start Comparing SUBSCRIBE message
(3) Start Comparing INVITE message \n";
$value = <STDIN>;
if ($value == 1 )
{
print "\n Start Comparing REGISTER message\n\n";
$IMS_Message = "VoLTE SIPTX: [SIPTX-SIP] ==> REGISTER";
#chomp ($IMS_Message);
}
elsif ($value == 2)
{
print "\n SUBSCRIBE message Flow\n\n";
}
elsif ($value == 3)
{
print "\n INVITE message Flow\n\n";
}
else
{
print "\nThe input is not valid!\n";
print "\nDo you want to continue selecting a Automation Mode again (Y or N)?\n";
$choice = <STDIN>;
if( $choice eq /[Yy]/) {
test_loop();
} else {
exit;
}
$kw = $IMS_Message;
$search = qr/\Q$kw/;
for my $file ( grep { -f } glob '*main_log' ) {
open my $fh, '<', $file or die qq{Unable to open "$file" for input: $!};
while ( <$fh> ) {
if ( /$search/ ) {
printf "Found keyword %s in file %s, line %d: %s\n", $kw, $file, $., $_;
last;
}
}
}
}
Here are some observations on your code
Your approach to debugging appears to be to try things at random to see if they work. It would be far more fruitful to add diagnostic print statements so that you can compare variables' actual values with what you expect
Error and warning messages are useful information, and it is foolish to comment out use strict to make them go away
Don't call subroutines with an ampersand &. That hasn't been best practice for twenty years now
Lay your code out tidily and cinsistently, so that both you and any people you ask for help can read it easily. As it stands it is impossible to tell where blocks start and end without counting brace characters {...}
Variables should be declared with my as close as possible to their first point of use, and not all at once at the top of the file or subroutine
chomp is necessary only for strings that have been read from the terminal or from a file. It returns the number of characters removed, not the trimmed string
if( $choice =~ /[Yy]/ ) { ... } will check only whether the string contains a Y, so if the operator enters MARRY ME! it will return true. You should use string equality eq to check whether a single Y character has been typed
You shouldn't put scalar variables alone inside double quotes. At best it will make no difference, and just add noise to your code; at worst it will completely change the value of the variable. Just my $kw = $IMS_Message is correct
Unless you require non-sequential access to the contents of a file, it is best to use a while loop to read and process it line by line, rather than read the whole thing into an array and process each element of the array. This also allows you to use the built-in line number variable $. instead of implementing your own $l
The main problem is that you have derived $search from the result of chomp $kw, which sets $search to the number of characters removed by chomp. This is always zero because $kw is a copy of $IMS_Message, which has no newline at the end. That means you are checking all the lines of every file for the character 0, and not for the message that you intended. The correct way is my $search = quotemeta($kw) which you had in place but have commented out, presumably as a result of your policy of "debugging by guesswork"
Fixing these things, your code should look something like this
my $search = qr/\Q$kw/;
for my $file ( grep { -f } glob '*main_log' ) {
open my $fh, '<', $file or die qq{Unable to open "$file" for input: $!};
while ( <$fh> ) {
if ( /$search/ ) {
printf "Found keyword %s in file %s, line %d: %s\n", $kw, $file, $., $_;
last;
}
}
}

Perl - Use of uninitialized value in string

I started teaching myself Perl, and with the help of some Googling, I was able to throw together a script that would print out the file extensions in a given directory. The code works well, however, it will sometimes complain the following:
Use of uninitialized value $exts[xx] in string eq at get_file_exts.plx
I tried to correct this by initializing my array as follows: my #exts = (); but this did not work as expected.
#!/usr/bin/perl
use strict;
use warnings;
use File::Find;
#Check for correct number of arguments
if(#ARGV != 1) {
print "ERROR: Incorrect syntax...\n";
print "Usage: perl get_file_exts.plx <Directory>\n";
exit 0;
}
#Search through directory
find({ wanted => \&process_file, no_chdir => 1 }, #ARGV);
my #exts;
sub process_file {
if (-f $_) {
#print "File: $_\n";
#Get extension
my ($ext) = $_ =~ /(\.[^.]+)$/;
#Add first extension
if(scalar #exts == 0) {
push(#exts, $ext);
}
#Loop through array
foreach my $index (0..$#exts) {
#Check for match
if($exts[$index] eq $ext) {
last;
}
if($index == $#exts) {
push(#exts, $ext);
}
}
} else {
#print "Searching $_\n";
}
}
#Sort array
#exts = sort(#exts);
#Print contents
print ("#exts", "\n");
You need to test if you found an extension.
Also, you should not be indexing your array. You also do not need to manage 'push' just do it. It is not the Perl way. Your for loop should start like this:
sub process_file {
if (-f $_) {
#print "File: $_\n";
#Get extension
my ($ext) = $_ =~ /(\.[^.]+)$/;
# If we found an extension, and we have not seen it before, add it to #exts
if ($ext) {
#Loop through array to see if this is a new extension
my $newExt = 1;
for my $seenExt (#exts) {
#Check for match
if ($seenExt eq $ext) {
$newExt = 0
last;
}
}
if ($newExt) {
push #exts,$ext;
}
}
}
}
But what you really want to do is to use a hash table to record if you saw an extension
# Move this before find(...); if you want to initialize it or you will clobber the
# contents
my %sawExt;
sub process_file {
if (-f $_) {
#print "File: $_\n";
# Get extension
my ($ext) = $_ =~ /(\.[^.]+)$/;
# If we have an extension, mark that we've seen it
$sawExt{$ext} = 1
if $ext;
}
}
# Print the extensions we've seen in sorted order
print join(' ',sort keys %sawExt) . "\n";
Or even
sub process_file {
if (-f $_ && $_ =~ /(\.[^.]+)$/) {
$sawExt{$1} = 1;
}
}
Or
sub process_file {
$sawExt{$1} = 1
if -f && /(\.[^.]+)$/;
}
Once you start thinking in Perl this is the natural way to write it
The warning is complaining about a content of $exts[xx], not #exts itself.
Actually $ext can be undef, when the filename doesn't match to your regexp, for instance README.
Try like:
my ($ext) = $_ =~ /(\.[^.]+)$/ or return;
The main problem is that you aren't accounting for file names that don't contain a dot, so
my ($ext) = $_ =~ /(\.[^.]+)$/;
sets $ext to undef.
Despite the warning, processing continues by evaluating undef as the null string, failing to find that in #exts, and so percolating undef to the array as well.
The minimal change to get your code working is to replace
my ($ext) = $_ =~ /(\.[^.]+)$/;
with
return unless /(\.[^.]+)$/;
my $ext = $1;
But there is a couple of Perl lessons to be learned here. It used to be taught that good programs were well-commented programs. That was in the days of having to write efficient but incomprehensible code, but is no longer true. You should write code that is as clear as possible, and add comments only if you absolutely have to write something that isn't self-explanatory.
You should remember and use Perl idioms, and try to forget most C that you knew. For instance, Perl accepts the "here document" syntax, and it is common practice to use or and and as short-circuit operators. Your parameter check becomes
#ARGV or die <<END;
ERROR: Incorrect syntax...
Usage: perl get_file_exts.plx <Directory>
END
Perl allows for clear but concise programming. This is how I would have written your wanted subroutine
sub process_file {
return unless -f and /(\.[^.]+)$/;
my $ext = $1;
foreach my $index (0 .. $#exts) {
return if $exts[$index] eq $ext;
}
push #exts, $ext;
}
Use exists on $exts[xx] before accessing it.
exists is deprecated though as #chrsblck pointed out :
Be aware that calling exists on array values is deprecated and likely
to be removed in a future version of Perl.
But you should be able to check if it exists (and not 0 or "") simply with :
if($exts[index] && $exts[$index] eq $ext){
...
}

perl array for non-zero values

Im trying to code a "service" script based on "ps".
my code:
#!/usr/bin/perl
use strict;
use warnings;
die "usage: $0 <service name>\n" unless $ARGV[0];
my $service = $ARGV[0];
open(my $ps, "ps -aux |") || die "Uknown command\n";
my #A = <$ps>;
close $ps;
foreach my $i(grep /$service/, #A){
chomp $i;
if($i=~ /root/){
next
}
print "$i\n";
}
My problem: When running the script against undef arg like:
$0 blablabla
I want to return an output if there is no such service appears/when returns 0
Thanks
I assume what you are asking is: How to give a proper message when no matching lines are found?
Well, just store the result in an array instead:
my #lines = grep { !/root/ && /$service/ } #A;
if (#lines) { # if any lines are found
for my $line (#lines) {
...
}
} else {
print "No match for '$service'!\n";
}
Or you can print the number of matches regardless of their number:
my $found = #lines;
print "Matched found: $found\n";
Note also that you can add the check for root in your grep.
As a side note, this part:
die "usage: $0 <service name>\n" unless $ARGV[0];
my $service = $ARGV[0];
Is perhaps better written
my $service = shift;
die "usage ...." unless defined $service;
Which specifically checks if the argument is defined or not, as opposed to true or not.
If I understand you correctly, you want to inform the user if no such service was found? If so, you can modify the script as follows:
my $printed; # Will be used as a flag.
foreach my $i(grep /$service/, #A){
chomp $i;
if($i=~ /root/){
next
}
$printed = print "$i\n"; # Set the flag if the service was found.
}
warn "No service found\n" unless $printed;
You can try something like this:
my #processes = grep /$service/, #A;
if ( scalar #processes ) {
foreach my $i( #processes ){
chomp $i;
if($i=~ /root/){
next;
}
print "$i\n";
}
}
else {
print 'your message';
}
You could check the result of the grep command before traversing it in the for loop, like:
...
my #services = grep { m/$service/ } #A;
# Filter the perl process running this script and...
if ( ! #services ) {
print "No service found\n";
exit 0;
}
foreach my $i( #services ){
...
}
Take into account that the grep command will never give a false return because it is including the perl process, so you will have to filter it, but I hope you get the idea.

Display And Pass Command Line Arguments in Perl

I have the following program "Extract.pl", which opens a file, finds the lines containing "warning....", "info...", "disabling..." then counts and prints the value and number of them. It is working ok.
What I want to do is to create command line arguments for each of the 3 matches - warning, disabling and infos and then run either of them from the command prompt.
Here is the code:
#!/usr/bin/perl
use strict;
use warnings;
my %warnings = ();
my %infos = ();
my %disablings = ();
open (my $file, '<', 'Warnings.txt') or die $!;
while (my $line = <$file>) {
if($line =~ /^warning ([a-zA-Z0-9]*):/i) {
++$warnings{$1};
}
if($line =~ /^disabling ([a-zA-Z0-9]*):/i) {
++$disablings{$1};
}
if($line =~ /^info ([a-zA-Z0-9]*):/i) {
++$infos{$1};
}
}
close $file;
foreach my $w (sort {$warnings{$a} <=> $warnings{$b}} keys %warnings) {
print $w . ": " . $warnings{$w} . "\n";
}
foreach my $d (sort {$disablings{$a} <=> $disablings{$b}} keys %disablings) {
print $d . ": " . $disablings{$d} . "\n";
}
foreach my $i (sort {$infos{$a} <=> $infos{$b}} keys %infos) {
print $i . ": " . $infos{$i} . "\n";
}
The builtin special array #ARGV holds all command line arguments to the script, excluding the script file itself (and the interpreter, if called as perl script.pl). In the case of a call like perl script.pl foo bar warnings, #ARGV would contain the values 'foo', 'bar', and 'warnings'. It's a normal array, so you could write something like (assuming the first argument is one of your options):
my ($warning, $info, $disabling);
if ($ARGV[0] =~ /warning/i) { $warning = 1 }
elsif ($ARGV[0] =~ /info/i) { $info = 1 }
elsif ($ARGV[0] =~ /disabling/i) { $disabling = 1 }
# [...] (opening the file, starting the main loop etc...)
if ( $warning and $line =~ /^warning ([a-zA-Z0-9]*)/i ) {
++$warnings{$1};
}
elsif ( $info and $line =~ /^info ([a-zA-Z0-9]*)/i ) {
++$infos{$1};
}
elsif ( $disabling and $line =~ /^disabling ([a-zA-Z0-9]*)/i ) {
++$disablings{$1};
}
I created flag variables for the three conditions before the main loop that goes through the file to avoid a regex compilation on every line of the file.
You could also use the Getopt::Long or Getopt::Std modules. These provide easy and flexible handling of the command line arguments.