Perl: Get key value - perl

I'm trying to get key values from hash inside my module:
Module.pm
...
my $logins_dump = "tmp/logins-output.txt";
system("cat /var/log/secure | grep -n -e 'Accepted password for' > $logins_dump");
open (my $fh, "<", $logins_dump) or die "Could not open file '$logins_dump': $!";
sub UserLogins {
my %user_logins;
while (my $array = <$fh>) {
if ($array =~ /Accepted\s+password\s+for\s+(\S+)/) {
$user_logins{$1}++;
}
}
return \%user_logins;
}
sub CheckUserLogins {
my $LoginCounter;
my $UsersToCheck = shift #_;
if (exists %{UserLogins()}{$UsersToCheck}){
$LoginCounter = %{UserLogins{$UsersToCheck}}; #How many logins?
}
else {
$LoginCounter = "0";
}
return \$LoginCounter;
}
Script.pl
$UserLoginCounter = Module::CheckUserLogins($UsersToPass);
I pass usernames to script and check if username is in hash, if it is, I need to return number of logins, which I'm trying to do with $LoginCounter. For some reason scripts returns only 0 or undef.

Well, for starters - you've got CheckUserLogins not CheckLoginAttempts.
Assuming that's just a typo - UserLogins returns a hash reference - a single scalar value. You're getting 0 if the exists check fails presumably.
If it does exist though, you're doing this:
$LoginCounter = %{UserLogins{$UsersToCheck}};
Which isn't valid. Do you have strict and warnings turned on? Because you're trying to assign a hash to a scalar, which isn't going to do what you want.
You probably mean:
$LoginCounter = ${UserLogins()} -> {$UsersToCheck};
Which dereferences the reference from UserLogins and then looks up a key.
I might however, approach your problem a little differently - it'll only work once when you do what you're doing, because each time you call UserLogins it creates a new hash, but you don't rewind $fh.
So I'd suggest:
use strict;
use warnings;
{
my %userlogins;
sub inituserlogins {
open( my $fh, "<", '/var/log/secure' )
or die "Could not open file: $!";
while ( my $array = <$fh> ) {
if ( $array =~ /Accepted\s+password\s+for\s+(\S+)/ ) {
$userlogins{$1}++;
}
}
close($fh);
}
sub CheckUserLogins {
my ($UsersToCheck) = #_;
inituserlogins() unless %userlogins;
return $userlogins{$UsersToCheck} ? $userlogins{$UsersToCheck} : 0;
}
}

You mustn't use capital letters in lexical identifiers as Perl reserves them for global identifiers like package names
One of the main problems is that you're using
exists %{UserLogins()}{$UsersToCheck}
which should be
exists UserLogins()->{$UsersToCheck}
or
exists ${UserLogins()}{$UsersToCheck}
Do you have use strict and use warnings in place as you should have?
Another problem is that you will read all the way through the file every time you call UserLogins. That means the second and later calls to CheckUserLogins (which calls UserLogins) will find nothing, as the end of the file has been reached
You should call your suibroutine user_logins and call it just once, storing the result as a scalar variable. This program shows how
use strict;
use warnings;
use v5.14; # For state variables
sub user_logins {
open my $fh, '<', '/var/log/secure' or die $!;
my %user_logins;
while ( <$fh> ) {
if ( /Accepted\s+password\s+for\s+(\S+)/ ) {
++$user_logins{$1};
}
}
\%user_logins;
}
sub check_user_logins {
my ($users_to_check) = #_;
state $user_logins = user_logins();
$user_logins->{$users_to_check} // 0;
}

Related

How to distinguish between "0" and NULL in perl?

Here we are looking for the string "reftext" in the given file. The line next to this contains a string with 3 integers. So we are extracting them in #all_num. We are printing the value of #all_num[2] only if is not NULL. But the logic used here doesn't print #all_num[2] even if it has 0.
#!/usr/bin/perl
open( READFILE, "<myfile.txt" );
#list = <READFILE>;
$total_lines = scalar #list;
for ( $count = 0; $count < $total_lines; $count++ ) {
if (#list[ $count =~ /reftext/ )
{
#all_num = #list[ $count + 1 ] =~ /(\d+)/g;
if ( #all_num[2] != NULL ) {
print "#all_num[2]\n";
}
}
}
Hope this helps,
use strict;
use warnings;
my #fvals = (
[ i => undef ],
[ j => 0 ],
[ k => "" ],
);
for my $r (#fvals) {
my ($k, $v) = #$r;
if (!defined($v)) { print "$k is undef\n"; }
elsif (!length($v)) { print "$k is empty string\n"; }
# elsif (!$v) { print "$k is zero\n"; }
# recognizes zero value in "0.0" or "0E0" notation
elsif ($v == 0) { print "$k is zero\n"; }
}
output
i is undef
j is zero
k is empty string
Perl does not include a NULL, so the line
if(#all_num[2]!= NULL)
is nonsensical in Perl. (More accurately, it attempts to locate a sub named NULL and run it to get the value to compare against #all_num[2], but fails to do so because you (presumably) haven't defined such a sub.) Note that, if you had enabled use strict, this would cause a fatal error instead of pretending to work. This is one of the many reasons to always use strict.
Side note: When you pull a value out of an array, it's only a single value, so you should say $all_num[2] rather than #all_num[2] when referring to the third element of the array #all_num. (Yes, this is a little confusing to get used to. I hear that it's been changed in Perl 6, but I'm assuming you're using Perl 5 here.) Note that, if you had enabled use warnings, it would have told you that "Scalar value #all_num[2] better written as $all_num[2]". This is one of the many reasons to always use warnings.
If you want to test whether $all_num[2] contains a value, the proper way to express that in Perl is
if (defined $all_num[2])
This is how your program would look using best practices
You should
Always use strict and use warnings, and declare all your variables with my
Use the three-parameter form of open
Check that open calls succeeded, and include $! in the die string if not
Use a while loop to process a file one line at a time, in preference to reading the entire file into memory
#!/usr/bin/perl
use strict;
use warnings;
open my $fh, '<', 'myfile.txt' or die $!;
while ( <$fh> ) {
next unless /reftext/;
my $next_line = <$fh>;
my #all_num = $next_line =~ /\d+/g;
print "$all_num[2]\n" if defined $all_num[2];
}
Try this:
#!/usr/bin/perl
use warnings;
use strict;
open(READFILE, "<", "myfile.txt") or die $!;
my #list = <READFILE>;
my $total_lines = scalar #list;
close (READFILE);
for(my $count=0; $count<$total_lines; $count++)
{
if($list[$count] =~ /reftext/)
{
my #all_num = $list[$count+1] =~ /(\d+)/g;
if($all_num[2] ne '')
{
print "$all_num[2]\n";
}
}
}
To check a variable is null or not:
if ($str ne '')
{
print $str;
}
or better:
my ($str);
$str = "";
if (defined($str))
{
print "defined";
}
else
{
print "not defined";
}
If the other answers do not work, try treating the variable as a string:
if ( $all_num[2] == 'null' && length($all_num[2]) == 4 ){
# null
} else {
# not null
}
As with any code you write, be sure to test your code.

How to show matching and Miss matching records of two text files in command prompt using Perl?

I'm using two text files sampleA.txt and sampleB.txt. I have two fields in each file and I need to compare first record(first row) of sampleA.txt with the first row of sampleB.txt and I want to show matching records as well as miss matching records in command prompt.I need to do that in Perl.
Using the below script I'm getting one output but it is wrong. I need to populate both matching as well as mismatching. How to do that?
sampleA.txt:
1|X
2|A
4|Z
5|A
sampleB.txt:
2|A
2|X
3|B
4|C
Output I'm getting:
2|A
2|X
4|C
Outputs I want:
Matching-Output:
2|A
Miss-matching-Output:
1|X
4|Z
5|A
3|B
4|C
Perl Script:
#!/usr/bin/perl
use strict;
use warnings;
open(FILE1,'C:\Users\sathiya.kumar\Desktop\sampleA.txt') || die $!;
open(FILE2,'C:\Users\sathiya.kumar\Desktop\sampleB.txt') || die $!;
my $interline;
while (my $line= <FILE1>) {
my #fields = split('\|',$line);
parser($fields[0]);
}
sub parser {
my $mergeid = shift;
while (defined $interline || ($interline= <FILE2>)) {
my #fields = split('\|',$interline);
my $key = $fields[0];
if ($key lt $mergeid) {
# Skip non-matching records
$interline = undef;
next;
} elsif ($key gt $mergeid) {
# wait for next key
last;
} else {
print $interline;
$interline = undef;
}
}
}
close(FILE1);
close(FILE2);
Let me know if you need more information.
You left out 2|X:
use strict;
use warnings;
use 5.016;
use Data::Dumper;
#Create a set from the entries in sampleA.txt:
my $fname = 'sampleA.txt';
open my $A_INFILE, '<', $fname
or die "Couldn't open $fname: $!";
my %a;
while (my $line = <$A_INFILE>) {
chomp $line;
$a{$line} = undef;
}
close $A_INFILE;
say Dumper(\%a);
#Create a set from the entries in sampleB.txt:
$fname = 'sampleB.txt';
open my $B_INFILE, '<', $fname
or die "Couldn't open $fname: $!";
my %b;
while (my $line = <$B_INFILE>) {
chomp $line;
$b{$line} = undef;
}
close $B_INFILE;
say Dumper(\%b);
#Divide the entries in both files into matches and mismatches:
my (#matches, #mismatches);
for my $a_val (keys %a) {
if (exists $b{$a_val}) {
push #matches, $a_val;
}
else {
push #mismatches, $a_val;
}
}
for my $b_val (keys %b) {
if (not exists $a{$b_val}) {
push #mismatches, $b_val;
}
}
say Dumper(\#matches);
say Dumper(\#mismatches);
--output:--
$VAR1 = {
'5|A' => undef,
'4|Z' => undef,
'1|X' => undef,
'2|A' => undef
};
$VAR1 = {
'2|X' => undef,
'3|B' => undef,
'4|C' => undef,
'2|A' => undef
};
$VAR1 = [
'2|A'
];
$VAR1 = [
'5|A',
'4|Z',
'1|X',
'2|X',
'3|B',
'4|C'
];
If you evaluate a hash in scalar context, it returns false if the hash is empty. If there are any key/value pairs, it returns true; more precisely, the value returned is a string consisting of the number of used buckets and the number of allocated buckets, separated by a slash. This is pretty much useful only to find out whether Perl's internal hashing algorithm is performing poorly on your data set. For example, you stick 10,000 things in a hash, but evaluating %HASH in scalar context reveals "1/16" , which means only one out of sixteen buckets has been touched, and presumably contains all 10,000 of your items. This isn't supposed to happen. If a tied hash is evaluated in scalar context, the SCALAR method is called (with a fallback to FIRSTKEY ).
http://perldoc.perl.org/perldata.html

function call in perl

As a part of my course work I have been learning perl programming language for the first time in last the few weeks. I have been writing small functions and making function calls. I have written a function for string matching.
use strict;
use warnings;
sub find_multi_string {
my ($file, #strings) = #_;
my $fh;
open ($fh, "<$file");
#store the whole file in an array
my #array = <$fh>;
for my $string (#strings) {
if (grep /$string/, #array) {
next;
} else {
die "Cannot find $string in $file";
}
}
return 1;
}
find_multi_string('file name', 'string1','string2','string3','string4','string 5');
In the above script I'm passing the arguments in the function call. The script works.
But I'd like to know if there is way to specify the file name and string1... string n in an array in the program itself and just make the function call.
find_multi_string();
That would be a mistake, always pass parameters and return values to your subroutines.
What you're describing is essentially using subroutines solely to subdivide and document your code. If you were to do that, it would better to just remove the subroutine entirely and include a comment before the section of code.
Overall, your code looks good as is. You probably will want to use quotemeta though, and your logic can be simplified a little:
use strict;
use warnings;
use autodie;
sub find_multi_string {
my ($file, #strings) = #_;
# Load the file
my $data = do {
open my $fh, "<", $file;
local $/;
<$fh>
};
for my $string (#strings) {
if ($data !~ /\Q$string/) {
die "Cannot find $string in $file";
}
}
return 1;
}
find_multi_string('file name', 'string1','string2','string3','string4','string 5');
A few improvements of your original code:
use autodie
use 3-args open
as you want to check anywhere in the file, just load the file as a single string
if the matching string are just text without metacharacters from regexp, just use the index function
Your question is about passing the function arguments from your program.
I suspect that you are looking for #ARGV. See perlvar.
Here is the modified code:
use strict;
use warnings;
use autodie;
sub find_multi_string {
my ($file, #strings) = #_;
my $content = do {
open my $fh, '<', $file;
local $/;
<$fh>
};
foreach (#strings) {
die "Cannot find $string in $file" unless index($content, $_) >= 0;
}
return 1;
}
find_multi_string(#ARGV);

How to write a correct name using combination of variable and string as a filehandler?

I want to make a tool to classify each line in input file to several files
but it seems have some problem in naming a filehandler so I can't go ahead , how do I solve?
here is my program
ARGV[0] is the input file
ARGV[1] is the number of classes
#!/usr/bin/perl
use POSIX;
use warnings;
# open input file
open(Raw,"<","./$ARGV[0]") or die "Can't open $ARGV[0] \n";
# create a directory class to store class files
system("mkdir","Class");
# create files for store class informations
for($i=1;$i<=$ARGV[1];$i++)
{
# it seems something wrong in here
open("Class$i",">","./Class/$i.class") or die "Can't create $i.class \n";
}
# read each line and random decide which class to store
while( eof(Raw) != 1)
{
$Line = readline(*Raw);
$Random_num = ceil(rand $ARGV[1]);
for($k=1;$k<=$ARGV[1];$k++)
{
if($Random_num == $k)
{
# Store to the file
print "Class$k" $Line;
last;
}
}
}
for($h=1;$h<=$ARGV[1];$h++)
{
close "Class$h";
}
close Raw;
thanks
Later I use the advice provided by Bill Ruppert
I put the name of filehandler into array , but it seems appear a syntax bug , but I can't correct it
I label the syntax bug with ######## A syntax error but it looks quite OK ########
here is my code
#!/usr/bin/perl
use POSIX;
use warnings;
use Data::Dumper;
# open input file
open(Raw,"<","./$ARGV[0]") or die "Can't open $ARGV[0] \n";
# create a directory class to store class files
system("mkdir","Class");
# put the name of hilehandler into array
for($i=0;$i<$ARGV[1];$i++)
{
push(#Name,("Class".$i));
}
# create files of classes
for($i=0;$i<=$#Name;$i++)
{
$I = ($i+1);
open($Name[$i],">","./Class/$I.class") or die "Can't create $I.class \n";
}
# read each line and random decide which class to store
while( eof(Raw) != 1)
{
$Line = readline(*Raw);
$Random_num = ceil(rand $ARGV[1]);
for($k=0;$k<=$#Name;$k++)
{
if($Random_num == ($k+1))
{
print $Name[$k] $Line; ######## A syntax error but it looks quite OK ########
last;
}
}
}
for($h=0;$h<=$#Name;$h++)
{
close $Name[$h];
}
close Raw;
thanks
To quote the Perl documentation on the print function:
If you're storing handles in an array or hash, or in general whenever you're using any expression more complex than a bareword handle or a plain, unsubscripted scalar variable to retrieve it, you will have to use a block returning the filehandle value instead, in which case the LIST may not be omitted:
print { $files[$i] } "stuff\n";
print { $OK ? STDOUT : STDERR } "stuff\n";
Thus, print $Name[$k] $Line; needs to be changed to print { $Name[$k] } $Line;.
How about this one:
#! /usr/bin/perl -w
use strict;
use POSIX;
my $input_file = shift;
my $file_count = shift;
my %hash;
open(INPUT, "<$input_file") || die "Can't open file $input_file";
while(my $line = <INPUT>) {
my $num = ceil(rand($file_count));
$hash{$num} .= $line
}
foreach my $i (1..$file_count) {
open(OUTPUT, ">$i.txt") || die "Can't open file $i.txt";
print OUTPUT $hash{$i};
close OUTPUT;
}
close INPUT;

How can match the first value of #ARGV to an array of possible options

I am trying to figure a way to capture the first argument from #ARGV and check its validity by checking it against an array of known valid arguments. I thought I could do this with a simple foreach loop but I realized this won't work because it will fail when the first invalid match comes back, which for my example script is the second argument.
Here the code that pertains to the problem, its concept script so there is not much.
my $primary_mode = $ARGV[0];
primary_mode_check($primary_mode);
sub primary_mode_check {
my #program_modes = ('create', 'destroy');
my $selected_mode = shift;
foreach my $mode (#program_modes) {
if ($selected_mode ne $mode) {
die RED "\'$selected_mode\' is not a valid program mode!\n";
}
}
}
Is there another way to accomplish the same idea? I am already using Getopt::Long in combonation with #ARGV to achieve a certain style. So I am focused on wanting to make this work.
UPDATE
I was thinking maybe I could match against regex, is that a possibility?
my $primary_mode = $ARGV[0] or die "No mode provided";
primary_mode_check($primary_mode);
sub primary_mode_check {
my $selected_mode = shift;
my #program_modes = ('create', 'destroy');
die "'$selected_mode' is not a valid program mode!\n"
unless grep { $selected_mode eq $_ } #program_modes;
}
If you are using perl 5.10 or greater:
use v5.10;
my $primary_mode = $ARGV[0] or die "No mode provided";
my #program_modes = qw(create destroy);
die "'$selected_mode' is not a valid program mode!\n"
unless $primary_mode ~~ #program_modes;
You code: Die if the arg doesn't match one of the allowed modes.
You want: Die if the arg doesn't match any of the allowed modes.
Put differently: Don't die if the arg matches one of the allowed modes.
my #program_modes = qw( create destroy );
sub primary_mode_check {
my ($selected_mode) = #_;
for my $mode (#program_modes) {
return if $selected_mode eq $mode;
}
die "'$selected_mode' is not a valid program mode!\n";
}
But a hash simplifies things a bit.
my %program_modes = map { $_ => 1 } qw( create destroy );
sub primary_mode_check {
my ($selected_mode) = #_;
die "'$selected_mode' is not a valid program mode!\n"
if !$program_modes{$selected_mode};
}
You might find App::Cmd useful for easy writing of application with commands.
I would recommend going with a hash of allowed modes. In addition, pass the allowed modes to the function rather than embedding them in the function.
You can also use grep for this purpose if the allowed modes are in an array:
#!/usr/bin/env perl
use warnings; use strict;
my ($primary_mode) = #ARGV;
my $allowed_modes = [qw(create destroy)];
check_primary_mode($primary_mode, $allowed_modes)
or die sprintf "%s is not a valid program mode\n", $primary_mode;
sub check_primary_mode {
my ($mode, $allowed) = #_;
return grep $mode eq $_, #$allowed;
}
However, grep will go through the entire array even though we need just one match. List::MoreUtils::first_index will short-circuit once a match is found (it does not matter if you have only two possible modes, but in the more general case, it might):
use List::MoreUtils qw( first_index );
...
sub check_primary_mode {
my ($mode, $allowed) = #_;
return (0 <= first_index { $mode eq $_ } #$allowed);
}
my $primary_mode = $ARGV[0];
primary_mode_check($primary_mode);
sub primary_mode_check {
my %program_modes; #program_modes{qw(create destroy)}=();
my $selected_mode = shift;
die RED "\'$selected_mode\' is not a valid program mode!\n"
unless exists $program_modes{$selected_mode};
}
I often use this idiom in such a case:
use strict;
use warnings;
my $cmd = shift #ARGV;
my #allowed = qw/ install uninstall check purge /;
die "Cannot understand command" unless ( grep { $cmd eq $_ } #allowed );
Edit: reading more carefully it looks quite a bit like Sinan's, and he's right, using first would search faster in a large list of possible ops.
Yes, a regular expression should work. For example:
my #modes = ('create', 'destroy');
my $regexp = join "|", #modes;
if ($selected =~ /^(?:$regexp)\z/) {
print "Found program mode '$1'\n";
} else {
die RED "\'$selected\' is not a valid program mode!\n";
}