Hash keys/values as function parameters - perl

I have the following perl code:
#!/usr/bin/perl
use strict;
use warnings;
use Net::SSH::Perl;
sub SSH($$$$){
my ($host, $user, $pass, $cmd) = #_;
my $ssh = Net::SSH::Perl->new($host);
$ssh->login("$user", "$pass");
return $ssh->cmd($cmd);
};
my %h = (
"ILSW01" => {
"ip" => "1.1.1.",
"user" => "bla",
"pass" => "xxx"
}
);
SSH($h{host}{ip}, $h{host}{user}, $h{host}{pass}, "ls -ltr")
Works fine.
My question is, what if i have more than one host, how can i use the keys of the "host's hash" as parameters in the SSH function to avoid duplicate code writing ?
To to be clear: I want avoid using the SSH function per host

I want avoid using the SSH function per host
That sounds like a very poor design as you'd be combining unrelated two tasks in one function. I'd personally go with
my %hosts = (
"ILSW01" => {
host => "1.1.1.",
user => "bla",
pass => "xxx",
},
...
);
sub SSH {
my ($connect_info, $cmd) = #_;
my $ssh = Net::SSH::Perl->new($connect_info->{host});
$ssh->login($connect_info->{user}, $connect_info->{pass});
return $ssh->cmd($cmd);
}
my #results = map { SSH($_, $cmd) } values(%hosts);
You can make a sub that consists of that last line if you really want a single sub, but I'm not sure there's much benefit to that here.
sub SSH_many {
my ($hosts) = #_;
return map { SSH($_, $cmd) } values(%$hosts);
}
my #results = SSH_many(\%hosts);

First, don't use prototyping in Perl functions. It is broken. Well... not broken, it doesn't work the way you think it should work.
You want to pass multiple hashes into your subroutine. Each containing three values:
The IP address
The User Name
The Password
In Perl, the basic data structures all pass scalar data. Variables like $foo contain a single piece of scalar data. Variables like #foo contain a list of scalar data, and variables like %foo contain a key/value set of scalar data. However, not all data is scalar. Some of it has a complex structure. The question is how to emulate this structure in Perl.
The way we do this in Perl is through references. A reference is a location in memory where my data is stored.
For example, my FTP location is a hash with three pieces of data:
my %ftp_user;
$ftp_user{IP} = 1.1.1.1;
$ftp_user{USRER} = "foo";
$ftp_user{PASSWORD} = "swordfish";
I can get a reference to this hash by putting a backslash in front of it:
my $ftp_user_ref = \%ftp_user;
Note the $ in front of the $ftp_user_ref variable. This means that the value in $ftp_user_ref is a scalar bit of data. I can put that into an array:
push #ftp_users_list, $ftp_user_ref;
Now my #ftp_users_list contains an array of entire hashes, and not an array of individual scalar pieces of data.
I can even take a reference of my #ftp_users_list:
my $ftp_user_list_ref = \#ftp_users_list;
And, I can pass that into my subroutine as a single piece of data.
Take a look at the Perl Reference Tutorial that comes with Perl and learn how references work and how you can use them.
Meanwhile, here's an example that uses references to call each host and run a series of commands. Note that my ssh subroutine takes just two parameters: An array of hashes that are my login credentials for each system, and a list of my commands I want to run on each system:
#! /usr/bin/env perl
#
use strict;
use warnings;
use feature qw(say);
my #ftp_list;
while ( my $line = <DATA> ) {
chomp $line;
my ($ip, $user, $password) = split /:/, $line;
# Push an anonymous hash into my #ftp_list
push #ftp_list, { IP => $ip, USER => $user, PASSWORD => $password };
}
my #commands;
push #commands, "cd data";
push #commands, "get foo.txt";
push #commands, "del foo.txt";
ssh( \#ftp_list, \#commands );
sub ssh {
my $ftp_list_ref = shift;
my $commands_ref = shift;
my #ftp_list = #{ $ftp_list_ref };
my #commmands = #{ $commands_ref };
for my $ip_ref ( #ftp_list ) {
my $ip = $ip_ref->{IP};
my $user = $ip_ref->{USER};
my $pass = $ip_ref->{PASSWORD};
say "my \$ssh = Net::SSH::Perl->new($ip);";
say "\$ssh->login($user, $pass);";
for my $command ( #commands ) {
my $results = say "\$ssh->cmd($command);";
return if not defined $results;
}
}
return 1;
}
__DATA__
1.1.1.1:bob:swordfish
1.2.3.2:carol:secret
1.3.4.5:lewis:ca$h

You could just pass your Hash of hosts and metadata to your SSH function:
sub SSH {
my (%h, $cmd) = #_;
my #commands;
for my $host ( keys %h ) {
my $ssh = New::SSH::Perl->new($host);
$ssh->login($h{$host}->{user}, $h{$host}->{pass});
push #commands,
$ssh->cmd($cmd);
}
return #commands;
}
Now you can call this function with as many hosts as you want, execute the remote commands and return a listing of the results;

Related

Why is the Hashref passed to the Net::Ping constructor, set to an empty hashref after Net::Ping->new($args)?

What am I missing here?
When passing arguments to Net::Ping like this, then $args and $args_copy will both be set to an empty hashref after initializing the constructor Net::Ping->new($args).
use strict;
use warnings;
use Data::Dumper qw(Dumper);
use Net::Ping;
sub _ping {
my ($args) = #_;
my $p = Net::Ping->new($args);
$p->close();
}
my $args = { proto => 'udp' };
my $args_copy = $args;
print Dumper $args; # $VAR1 = { 'proto' => 'udp' }
print Dumper $args_copy; # $VAR1 = { 'proto' => 'udp' }
_ping($args);
print Dumper $args; # $VAR1 = {}
print Dumper $args_copy; # $VAR1 = {}
I see the same behavior on both Strawberry Perl and WSL2 running Ubuntu 20.04.4 LTS with Perl v5.30.0.
This is interesting, a class (constructor) deleting caller's data.
The shown code passes a reference to the Net::Ping constructor and that data gets cleared, and right in the constructor (see below).
To avoid having $args cleared, if that is a problem, pass its copy instead
_ping( { %$args } );
This first de-references the hash and then constructs an anonymous hash reference with it,† and passes that. So $args is safe.
The constructor new uses data from #_ directly (without making local copies), and as it then goes through the keys it also deletes them, I presume for convenience in further processing. (I find that scary, I admit.)
Since a reference is passed to new the data in the calling code can get changed.‡
† When copying a hash (or array) with a complex data structure in it -- when its values themselves contain references -- we need to make a deep copy. One way is to use Storable for it
use Storable qw(dclone);
my $deep_copy = dclone $complex_data_structure;
Here that would mean _ping( dclone $args );. It seems that new can only take a reference to a flat hash (or scalars) so this wouldn't be necessary.
‡ When a sub works directly with the references it gets then it can change data in the caller
sub sub_takes_ref {
my ($ref_data) = #_;
for my $k (keys %$ref_data) {
$ref_data->{$k} = ...; # !!! data in caller changed !!!
}
}
...
my $data = { ... }; # a hashref
sub_takes_ref( $data );
However, if a local copy of arguments is made in the sub then caller's data cannot be changed
use Storable qw(dclone); # for deep copy below
sub sub_takes_ref {
my ($ref_data) = #_;
my $local_copy_of_data = dclone $ref_data;
for my $k (keys %$local_copy_of_data) {
$local_copy_of_data->{$k} = ...; # data in caller safe
}
}
(Just remember to not touch $ref_data but to use the local copy.)
This way of changing data in the caller is of course useful when the sub is meant to work on data structures with large amounts of data, since this way they don't have to be copied. But when that is not the purpose of the sub then we need to be careful, or just make a local copy to be safe.

How to push data to an array-containing hash with `eval` in perl?

I'm trying to mirror the website which having the files and folder to hash.
This one having example So I tried, the following
my $url = "http://localhost/mainfolder/";
my ($parent) = $url=~m/\/(\w+)\/?$/;
my %tree=(mainfolder=>[]);
folder_create($url);
sub folder_create
{
my $url = shift;
my $cont = get($url);
my ($child) = $url=~m/($parent.*)/;
$child=~s/\/?(\w+)\/?/{$1}/g;
while($cont=~m/(<tr.+?<\/tr>)/g)
{
my $line = $1;
if($line=~m/\[DIR\].*?href="([^"]*)"[^>]*>(.+?)<\/a>/)
{
my $sub =$1;
$sub=~s/\///;
print "$child\n\n";
push ( eval'#{$tree $child}',$sub);
}
}
}
use Data::Dumper;
print Dumper \%tree,"\n\n\n";
Update
Instead of messing with eval you should use the Data::Diver module
Because of the single quotes, you're trying to execute #{$hash$var} which isn't valid Perl.
If you wrote it as
push eval "\#{\$hash$var}", "somedata"
Then the eval would work, but it would evaluate to the contents of the array in hash element main, which is an empty list of values. That means your call would become
push( ( ), "somedata")
or just
push "somedata"
which is meaningless
This is a particularly unpleasant thing to want to do. Why do you think you need it?

Hook to provide a value for every Hash lookup in Perl

Is it possible to provide a hook in Perl to make sure no Hash key lookup fails ?
Example :
use strict;
use warnings;
my %hash_example = ( "a"=>"apple", "b"=>"ball" );
print $hash_example{"a"}; # Goes Fine.
print $hash_example{"c"}; # Throws Warning ( "Use of uninitialized value " ).
Codepad link
Whenever a hash lookup happens, some subroutine could get called which can provide a default value.
I mean, any hash lookup should call a sub ( say "get_hash_value (hash_ref, key) " ) and pass the hash and key to it. A sample of such a sub is shown below :
sub get_hash_value {
my $hash_ref = shift;
my $key = shift;
if ( exists $hash_ref->{$key} ) { # For Normal Lookup.
return $hash_ref->{$key};
}
else {
# This is the interesting place where we could provide our own values.
return "custom_value_based_on_certain_conditions"; # Some value
}
}
Another consequence would be the ability to alter the value returned against a key. We would be able to return a different value than what actually is stored against that key ( in that hash ).
There might not be a valid use case for this but am intrigued and would like to learn if such things are supported in Perl.
As said by Сухой27 in comment, this works fine:
my %hash_example = ( "a"=>"apple", "b"=>"ball" );
print $hash_example{"a"};
print $hash_example{"c"} // "custom_value_based_on_certain_conditions";
Doc on logical defined or
I would suggest that trying to alter how a hash lookup "works" is a really terrible idea, as a good way to create code that's hard to maintain.
However instead I would suggest you look at creating an object instead of a hash. They are basically the same thing, but an object includes code, and there is an expectation that the code within the object is 'doing it's own thing'.
So at a basic level:
#!/usr/bin/env perl
use strict;
use warnings;
package Hash_Ob;
sub new {
my ($class) = #_;
my $self = {};
bless( $self, $class );
return $self;
}
sub get_value {
my ( $self, $valuename ) = #_;
if ( $self->{$valuename} ) {
return $self->{$valuename};
}
else {
#generate your own value here!
$self->{$valuename} = 42;
return $self->{$valuename};
}
}
1;
Which you'd then 'call' using:
#!/usr/bin/env perl
use strict;
use warnings;
use Hash_Ob;
my $magic_hash = Hash_Ob -> new();
print $magic_hash -> get_value('new_value');
This avoids the problem of altering how a 'well known' mechanism actually works, and so future maintenance programmers will not curse your name.
Then maybe you want to use a tied hash. Tying is a mechanism to change the behavior of a builtin data type. See perltie for the gory details.
{
package HashWithDefault;
use Tie::StdHash;
our #ISA = qw(Tie::StdHash); # inherit STORE, FIRST, NEXT, etc.
sub TIEHASH {
my ($pkg,$default_val) = #_;
return bless { __default_val__ => $default_val}, $pkg;
}
sub FETCH {
my ($self,$key) = #_;
exists $self->{$key} ? $self->{$key} : $self->{__default_val__};
}
sub CLEAR { # don't clear the default val
my $self = shift;
%$self = ( __default_val__ => $self->{__default_val__} );
}
}
tie my %hash, 'HashWithDefault', "42";
%hash = (foo => 123, bar => 456);
print $hash{foo}; # 123
print $hash{quux}; # 42

Reference found where even-sized list expected in Perl - Possible pass-by-reference error?

I have a Perl class/module that I created to display Bible verses. In it there is a hash that stores several verses, with the key being the book/chapter/verse and the value being the text. This hash is returned from the module.
I'm including the Bible class in a controller class, and that connection seems to work. The problem is I keep getting errors on executing. My IDE because I'm following a Lynda tutorial, is Eclipse with the EPIC plugin.
The error is:
Reference found where even-sized list expected at C:/Documents and Settings/nunya/eric.hepperle_codebase/lynda/lamp/perl5/Exercise Files/14 Modules/eh_bibleInspiration_controller.pl line 42.
Use of uninitialized value $value in concatenation (.) or string at C:/Documents and Settings/nunya/eric.hepperle_codebase/lynda/lamp/perl5/Exercise Files/14 Modules/eh_bibleInspiration_controller.pl line 45.
HASH(0x19ad454) =>
Here is the CONTROLLER class:
#!/usr/bin/perl
# eh_bibleInspiration_controller.pl by Eric Hepperle - 06/23/13
#
use strict;
use warnings;
use Data::Dumper;
use EHW_BibleInspiration;
main(#ARGV);
sub main
{
my $o = EHW_BibleInspiration->new; # instantiate new object.
my %bo_ref = $o->getBibleObj();
print "\$o is type: " . ref($o) . ".\n";
print "\%bo_ref is type: " . ref(\%bo_ref) . ".\n";
# exit;
$o->getVerseObj();
listHash(\%bo_ref);
message("Done.");
}
sub message
{
my $m = shift or return;
print("$m\n");
}
sub error
{
my $e = shift || 'unkown error';
print("$0: $e\n");
exit 0;
}
sub listHash
{
my %hash = #_;
foreach my $key (sort keys %hash) {
my $value = $hash{$key};
message("$key => $value\n");
}
}
Here is the class that returns the verses and has the method to pick a random verse:
# EHW_BibleInspiration.pm
# EHW_BibleInspiration.
#
package EHW_BibleInspiration;
use strict;
use warnings;
use IO::File;
use Data::Dumper;
our $VERSION = "0.1";
sub new
{
my $class = shift;
my $self = {};
bless($self, $class); # turns hash into object
return $self;
}
sub getVerseObj
{
my ($self) = #_;
print "My Bible Verse:\n";
my $verses = $self->getBibleObj();
# get random verse
#$knockknocks{(keys %knockknocks)[rand keys %knockknocks]};
# sub mysub {
# my $params = shift;
# my %paramhash = %$params;
# }
# my %verses = %{$verses};
# my $random_value = %verses{(keys %verses)[rand keys %verses]};
# print Dumper(%{$random_value});
}
sub getBibleObj
{
my ($self) = #_;
# create bible verse object (ESV)
my $bibleObj_ref = {
'john 3:16' => 'For God so loved the world,that he gave his only Son, that whoever believes in him should not perish but have eternal life.',
'matt 10:8' => 'Heal the sick, raise the dead, cleanse lepers, cast out demons. You received without paying; give without pay.',
'Luke 6:38' => 'Give, and it will be given to you. Good measure, pressed down, shaken together, running over, will be put into your lap. For with the measure you use it will be measured back to you.',
'John 16:24' => 'Until now you have asked nothing in my name. Ask, and you will receive, that your joy may be full.',
'Psalms 32:7' => 'You are a hiding place for me; you preserve me from trouble; you surround me with shouts of deliverance. Selah',
'Proverbs 3:5-6' => 'Trust in the LORD with all your heart, and do not lean on your own understanding. 6 In all your ways acknowledge him, and he will make straight your paths.',
'John 14:1' => 'Let not your hearts be troubled. Believe in God; believe also in me.'
};
my $out = "The BIBLE is awesome!\n";
return $bibleObj_ref;
}
1;
What am I doing wrong? I suspect it has something to do with hash vs hash reference, but I don't know how to fix it. My dereferencing attempts had failed miserably because I don't really know what I'm doing. I modeled my random getter off of something I saw on perlmonks. #$knockknocks{(keys %knockknocks)[rand keys %knockknocks]};
In the main, you have:
my %bo_ref = $o->getBibleObj();
but, in package EHW_BibleInspiration;, the method getBibleObj returns : return $bibleObj_ref;
You'd do, in the main : my $bo_ref = $o->getBibleObj();
and then call listHash($bo_ref);
Finaly, don't forget to change sub listHash to:
sub listHash
{
my ($hash) = #_;
foreach my $key (sort keys %{$hash}) {
my $value = $hash->{$key};
message("$key => $value\n");
}
}
In your main, you do
listHash(\%bo_ref);
This passes a hash reference to the sub. However, it tries to unpack its arguments like
my %hash = #_;
Oops, it wants a hash.
Either, we pass it a hash: listHash(%bo_ref). This saves us much typing.
Or, we handle the reference inside the sub, like
sub listHash {
my ($hashref) = #_;
foreach my $key (sort keys %$hashref) {
my $value = $hashref->{$key};
print "$key => $value\n";
}
}
Notice how the reference uses the dereference arrow -> to access hash entries, and how it is dereferenced to a hash for keys.
I suspect that your suspicion is correct. In your method sub listHash you're correctly passing in the hash but you're trying to use a hash instead of a hash reference for the internal variable. Try using my ($hash) = #_; instead of my %hash = #_;.
When using references you can use the -> operator to de-reference it to get to the underlying values. The rest of your method should look like this:
sub listHash
{
my ($hash) = #_;
foreach my $key (sort keys %{$hash}) {
my $value = $hash->{$key};
message("$key => $value\n");
}
}
On line 43 of your program I had to tell Perl that the reference should be a hash reference by calling keys %{$hash}. Then on line 44 I de-referenced the hash to get the correct value by calling $hash->{$key}. For more information on Perl and references you can read the through the tutorial.
listHash is expecting a hash and you're passing it a hash reference. Change:
listHash(\%bo_ref);
to:
listHash(%bo_ref);

Perl - assign a hash ref in a hash

use 5.010;
use strict;
use warnings;
use JSON::XS;
use YAML::XS;
my %data = ();
my $content = <<HERE;
{
"name":"BLAHBLAH","contact":{"phone":"12345","twitter":"BLAHBLAH"},
"location": {"address":"NOTTELLING","lat":10,"lng":10,"postalCode":"1234",
"city":"BLAH","state":"BLAH","country":"BLAH"},
"categories":[{"id":"BLAH","name":"BLAH"}]
}
HERE
my $id = "name1";
sub function {
my ( $id, $data, $content ) = #_;
my %data = %$data;
my $out = decode_json($content);
say "out:", Dump $out;
$data{$id} = $out;
}
function( $id, \%data, $content );
say "data:", Dump %data;
This doesn't work as the way I expected. Can you please tell me why and how it will work?
"This doesn't work as the way i expected."
What were you expecting? Let's step through the errors:
1) date != data
2) $content=~m!(,*)! will leave $1 empty, since $content doesn't contain any commas.
3) decode_json($1) will throw a runtime error, since $1 is empty and decode_json() can only be applied to a properly formatted JSON string.
4) $id is not defined.
"Can you please tell me why and how it will work?"
It won't work, if that isn't clear yet. There are more errors than code there.
"how do I assign a hash ref into hash?"
Use the \ unary reference operator, eg:
my %h = ();
my %h2 = (
a => 10
);
$h{h2} = \%h2;
print $h{h2}->{a};
You can also declare a scalar ($) as a reference to an anonymous (unnamed) hash; here $hr is a reference, the hash itself has no symbol or name associated with it:
my $hr = {
n => 42
};
# as an existing hash member:
$h{h3} = {
x => 666,
# some other examples:
hr => \%h2,
hr2 => {
x => 1024
}
};
Notice curly braces {} used in the declaration instead of (). When you are nesting (anonymous) hashes as with hr2, always use that form.
If you search for perl hash tutorial you'll find more in-depth things.
The reason that you're not finding anything in the package-scoped %data (the one defined just after use YAML::XS) is because you're creating a brand-new and completely independent %data inside of function with the line
my %data = %$data;
This creates a new hash and copies the contents of the hash referenced by $data into it.
Try this instead:
sub function {
my ($id, $data, $content) = #_;
my $out = decode_json($content);
say "out:", Dump $out;
$data->{$id} = $out;
}
I think you have a typo:
function($id,/%data,$content);
must be
function($id,\%data,$content);
and $content is not a reference to %data hash, so in your function you should do:
my %data=%$data; # in place of "my %content=%$content;"