How to push data to an array-containing hash with `eval` in perl? - 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?

Related

How do I iterate over methods for Perl object

I've created an Object such as
my $hex = Hexagram->new();
and it has various methods:
top
bot
chinese
title
meaning
This object will be created numerous times and each time I need to gather and test information for each of the above methods.
I would like to do something like
foreach my $method ( qw/top bot chinese title meaning/ )
{
&gather_info($hex,$method);
}
and then have something like
sub gather_info {
my ($hex,$method) = #_;
print "What is the $method? ";
my $response = <STDIN>;
chomp $response;
$hex->${method}($reponse);
.... and other actions ....
}
But this doesn't work. Instead, for each method I seem to have to write out the basic code structure again and again which just seems plain wasteful.
I've also tried something where I try to pass a reference to the method call such as in
foreach my $ra ( [\$hex->top, "top"],
[\$hex->bot, "bot"],....)
{
my ($object_method, $name) = #{$ra};
&rgather_info($object_method, $name);
}
where
sub $gather_info {
my ($rhex, $name) = #_;
print "What is the $name?";
my $response = <STDIN>;
chomp $response;
&{$rhex}($response);
.... and other actions ....
}
But this time I get an error about
Not a CODE reference at <program name> line <line number>,....
Any suggestions on how I can do this?
According to perlobj method calls can be made using a string variable.
$object->$method( #args );
So your foreach loop should have worked fine. Or this one, which is much less wordy:
use strict;
use warnings;
my $hex = Hexagram->new();
gather_info( $hex, $_ )
for qw/top bot chinese title meaning/;
sub gather_info {
my ($hex, $method) = #_;
print "What is $method?\n";
my $response = <STDIN>;
chomp $response;
$hex->$method( $response );
}
Make sure you have strict and warnings enabled and try again. Update you post with errors, etc.

Hash keys/values as function parameters

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;

Push into end of hash in Perl

So what I am trying to do with the following code is push a string, let's say "this string" onto the end of each key in a hash. I'm completely stumped on how to do this. Here's my code:
use warnings;
use strict;
use File::Find;
my #name;
my $filename;
my $line;
my #severity = ();
my #files;
my #info = ();
my $key;
my %hoa;
my $xmlfile;
my $comment;
my #comments;
open( OUTPUT, "> $ARGV[0]" );
my $dir = 'c:/programs/TEST/Test';
while ( defined( $input = glob( $dir . "\\*.txt" ) ) ) {
open( INPUT, "< $input" );
while (<INPUT>) {
chomp;
if (/File/) {
my #line = split /:/;
$key = $line[1];
push #{ $hoa{$key} }, "Filename\n";
}
if ( /XML/ ... /File/ ) {
$xmlfile = $1;
push #{ $hoa{$key} }, "XML file is $xmlfile\n";
}
if (/Important/) {
push #{ $hoa{$key} }, "Severity is $_\n";
}
if (/^\D/) {
next if /Important/;
push #{ $hoa{$key} }, "Given comment is $_\n";
}
push #{ $hoa{$key} }, "this string\n";
}
}
foreach my $k ( keys %hoa ) {
my #list = #{ $hoa{$k} };
foreach my $l (#list) {
print OUTPUT $l, "\n";
}
}
}
close INPUT;
close OUTPUT;
Where I have "this string" is where I was trying to push that string onto the end of the array. However, what ended up happening was that it ended up printing "this string" three times, and not at the end of every key like I wanted. When I tried to put it outside the while() loop, it said that the value of $key was not initialized. So please, any help? And if you need any clarification on what I'm asking, just let me know. Thank you!
No offence, but there are so many issues in this code I don't even know where to start...
First, the 'initialization block' (all these my $something; my #somethings lines at the beginning of this script) is not required in Perl. In fact, it's not just 'redundant' - it's actually confusing: I had to move my focus back and forth every time I encountered a new variable just to check its type. Besides, even with all this $input var is still not declared as local; it's either missing in comments, or the code given has omissions.
Second, why do you declare your intention to use File::Find (good) - but then do not use it at all? It could greatly simplify all this while(glob) { while(<FH>) { ... } } routine.
Third, I'm not sure why you assign something to $key only when the line read is matched by /File/ - but then use its value as a key in all the other cases. Is this an attempt to read the file organized in sections? Then it can be done a bit more simple, either by slurp/splitting or localizing $/ variable...
Anyway, the point is that if the first line of the file scanned is not matched by /File/, the previous (i.e., from the previous file!) value is used - and I'm not quite sure that it's intended. And if the very first line of the first file is not /File/-matched, then an empty string is used as a key - again, it smells like a bug...
Could you please describe your task in more details? Give some test input/output results, perhaps... It'd be great to proceed in short tasks, organizing your code in process.
Your program is ill-conceived and breaks a lot of good practice rules. Rather than enumerate them all, here is an equivalent program with a better structure.
I wonder if you are aware that all of the if statements will be tested and possibly executed? Perhaps you need to make use of elsif?
Aside from the possibility that $key is undefined when it is used, you are also setting $xmlfile to $1 which will never be defined as there are no captures in any of your regular expressions.
It is impossible to tell from your code what you are trying to do, so we can help you only if you show us your output, input and say how to derive one from the other.
use strict;
use warnings;
use File::Find;
my ($outfile) = #ARGV;
my $dir = 'c:/programs/TEST/Test';
my %hoa;
my $key;
while (my $input = glob "$dir/*.txt") {
open my $in, '<', $input or die $!;
while (<$in>) {
chomp;
if (/File/) {
my $key = (split /:/)[1];
push #{ $hoa{$key} }, "Filename\n";
}
if (/XML/ ... /File/) {
my $xmlfile = $1;
push #{ $hoa{$key} }, "XML file is $xmlfile\n";
}
if (/Important/) {
push #{ $hoa{$key} }, "Severity is $_\n";
}
if (/^\D/) {
next if /Important/;
push #{ $hoa{$key} }, "Given comment is $_\n";
}
push #{ $hoa{$key} }, "this string\n";
}
close $in;
}
open my $out, '>', $outfile or die $!;
foreach my $k (keys %hoa) {
foreach my $l (#{ $hoa{$k} }) {
print $out $l, "\n";
}
}
close $out;
I suspect based on your code, that the line where $key is set is not called each time through the loop, and that you do not trigger any of the other if statements.
This would append "this string" to the end of the array. Based on that you are getting 3 of the "this strings" at the end of the array, I would suspect that two lines do not go through the if (/FILE/) or any of the other if statements. This would leave the $key value the same and at the end, you would append "this string" to the array, using whatever the last value of $key was when it was set.
This will append the string "this string" to every element of the hash %hoa, which elements are array refs:
for (values(%hoa)) { push #{$_}, "this string"; }
Put that outside your while loop, and you'll print "this string" at the end of each element of %hoa.
It will autovivify array refs where it finds undefined elements. It will also choke if it cannot dereference an element as an array, and will manipulate arrays by symbolic reference if it finds a simple scalar and is not running under strict:
my %autoviv = ( a => ['foo'], b => undef );
push #$_, "PUSH" for values %autoviv; # ( a => ['foo', 'PUSH'], b => ['PUSH'] )
my %fatal = ( a => {} );
push #$_, "PUSH" for values %fatal; # FATAL: "Not an ARRAY reference at..."
my %dangerous = (a => "foo");
push #$_, "PUSH" for values %dangerous; # Yikes! #foo is now ("PUSH")
use strict;
my %kablam = (a => "foo");
push #$_, "PUSH" for values %kablam; # "Can't use string ("foo") as an ARRAY ref ..."
As I understand it, traverse the hash with a map command to modify its keys. An example:
EDIT: I've edited because I realised that the map command can be assigned to the same hash. No need to create a new one.
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my %hash = qw|
key1 value1
key2 value2
key3 value3
|;
my %hash = map { $_ . "this string" => $hash{ $_ } } keys %hash;
print Dump \%hash;
Run it like:
perl script.pl
With following output:
$VAR1 = {
'key3this string' => 'value3',
'key2this string' => 'value2',
'key1this string' => 'value1'
};

Passing hashes from a package to a method in another package and manipulating it in Perl

I have two packages. There is one hash in one package. I want to pass this hash to a method in another package, manipulate it and see the results in the previous package. Here's my code:
{
package Statistical_Analysis;
use Moose;
our $data;
our $ref;
our $k;
our $v;
sub countUseCase
{
my ($self, $value, $hash) = #_;
print "Passed value: ".$value."\n";
print "Hash Address: ".$hash."\n";
$self->{ref} = $hash;
$self->{%$ref}{'country'} = "something";
#print "IP Address: ".$self->{data}."\n";
#print "Hash Value: ".$self->{ref{'ip_count'}}."\n";
}
}
{
package Parse;
use Moose;
our %ip_address;
sub getFields
{
our $stanalyze_obj = Statistical_Analysis->new();
my $ref = \%ip_address;
$stanalyze_obj->countUseCase($ref);
dispHashMap();
}
sub dispHashMap
{
print \%ip_address."\n";
while ( my ($k,$v) = each %ip_address )
{
print "$k => $v\n";
}
}
But I cant see the changes in the hash. Any help?
You don't see any change because you never change it. Since it makes no sense, I presume you meant to change the $ip_address{country} when you do
$self->{%$ref}{'country'} = 'something';
If so, that should be
$hash->{country} = 'something';
Of course, $hash is stored in $self->{ref}, so you could also use
$self->{ref}->{country} = 'something';
which can be shortened to
$self->{ref}{country} = 'something';
PS — What's with all the our variables? You should almost never have to use our. #ISA and #EXPORT_OK are about the only uses I can think of. All of those should be my.
PSS — Actually, almost none of those should exist at all. What's with declaring variables you don't even use? One of these declarations is making your error a lot less obvious.
It seems that you called countUseCase with only one parameter, $ref. Calling that method with only one parameter, causes $hash to be undef.

Problems deferencing the Hash value from Net::Twitter

Im using the Net::Twitter module from CPAN, but I'm having a small issue with it.
The following subroutine searches for a term on Twitter but I cant seem to get anything
but a HASH value (ie. %HASH(0x9096dc0) )
How do I go about getting just the contents of the tweet?
sub twit_search
{
my $term = shift #_;
my $page = 1;
my #results;
while (scalar #results < $opts{maxresults})
{
my $rset = $handle->search({query=>$term, page => $page, rpp => $opts{rpp} });
print "Searching for $term (page $page)\n" if $opts{verbose};
if (ref $rset eq 'HASH' && exists $rset->{results})
{
last unless #{$rset->{results}};
push #results, #{$rset->{results}};
printf "Now we have %d entries\n", scalar #results if $opts{verbose};
}
$page++;
}
foreach my $tweet (#results)
{
print $tweet;
}
}
What you're getting back is actually a hash reference, which can be used to get at the hash containing the actual data. You can use Data::Dumper; print Dumper($tweet); to see the full structure and contents of the tweet.
It's been a year or so since I last wrote Twitter-related code, but I believe the actual text of the tweet should be in the "text" key, so, to print that, use
print $tweet->{text};