I'm new to using modules in Perl. My head is exploding right now and i would like to know what is wrong in here:
#!/usr/bin/perl
use strict;
use Mail::Mailer;
my $from_adress = "email\#xxxxx.com";
my $to_adress = "email\#hxxxx.com";
my $subject = "There goes bananas\n";
my $body = "Here is the bananas";
my $server = "smtp.gmail.com";
my $mailer = Mail::Mailer->new("smtp", Server => $server);
$mailer->open({
From => $from_adress,
To => $to_adress,
Subject => $subject,
});
print $mailer $body;
$mailer->close();
open(F, '>>', $Mail::Mailer::testfile::config{outfile});
print F #_;
print #_;
close (F);
Sorry to post the whole script but i'm not sure where it went wrong. I don't get any print from #_ variable. I would love to receive advises on how to improve in using modules in Perl and how i can get better at it.
Thanks in advance.
Well done for using strict in your code. For extra credit, add a use warnings line too.
I can't see any obvious problems with the way you're using the module. Do you think there's something wrong? Is the email not being sent?
If you're not getting the email, then I'd suggest that your first step should be to follow the example in the documentation and change the close line to:
$mailer->close
or die "couldn't send whole message: $!\n";
I wonder if the problem (if there is one) is that you're using Google's SMTP server and you don't have permission to do that. Perhaps you need to authenticate first.
A few other points about your code.
There is no need for all of your set-up variables to be initialised with double-quoted strings. And if you switch to single-quoted strings then you no longer need to escape the #s in the data. You would need double quotes to put the newline in $subject, but I've removed that as email subject lines rarely contain newlines.
my $from_adress = 'email#xxxxx.com';
my $to_adress = 'email#hxxxx.com';
my $subject = 'There goes bananas';
my $body = 'Here is the bananas';
my $server = 'smtp.gmail.com';
The last four lines of your code are confusing in many ways. I'm not really sure what you're trying to achieve there. I'll point out two things though. Firstly, we generally use lexical filehandles these days. If you're learning from a source that uses bareword filehandles, then I'd worry slightly about its age. So the file opening line should look like this:
# $f is, of course, a terrible name for a variable
open(my $f, '>>', $Mail::Mailer::testfile::config{outfile});
You are then printing the value of #_. In Perl, #_ contains the arguments to a subroutine. And this code isn't inside a subroutine, so #_ will be empty. So I'm not surprised that you're not getting any output.
Lastly, I'll point out that I find that I enjoy working with email in Perl a lot more when I'm using tools from the Email::* namespace. In particular, I'd use Email::Sender for sending email.
Update: Ok, I've had a closer look at the Mail::Mailer documentation and I think I understand what you're trying to do in the last four lines. I think you're trying to write the mail message data to the file. Is that right?
If it is, then you're misunderstanding the documentation. The way to do that is to change the type that you pass to new(). It needs to be testfile rather than smtp. So change
my $mailer = Mail::Mailer->new("smtp", Server => $server);
to
my $mailer = Mail::Mailer->new("testfile",);
That will write the mail to a file called mailer.testfile and no mail will be sent.
Related
I thought to do this way. But Top_file not able to receive the data from sub_file.
Top_file.pl
my $top_file_data = "Hi from Top_file\n";
$ENV{'SEND_TO_SUB'} = $top_file_data;
system("perl sub_file.pl");
my $received_data =$ENV{'RECEIVED_DATA'};
say" Message Received from SUB : $received_data";
sub_file.pl
my $data =$ENV{'SEND_TO_SUB'};
say "Message Received from Top : $data";
$ENV{'RECEIVED_DATA'} = "Got Your Message";
Any suggestion how to do it?
If you want to communicate between two processes, use one of the many tools for that. The perlipc docs show many ways to do this.
Changes to the environment do not propagate upwards, but you can capture output with backticks. Note, you should use the same perl as the original process ($^X) because various environments may already be set and some other perl may be incompatible. Also, I've run into situations where $^X has whitespace, so I quote it:
my $message = `"$^X" sub_file.pl`;
I found a script online that I thought was going to do what I needed, but I can't get it to work as my PERL skills are pretty low. Basically, I need to monitor this URL on apple.com and make sure the download form is available, and if it isn't available, I need to receive an email saying that the form isn't available from $hostname, here is the traceroute from that host. The traceroute is important because Apple uses Akamai and some GeoIP magic for their downloads.
I'm open to keeping this script and adding on to it or doing it another way. Thanks for taking the time to look at this for me. I'll be sure to share the finished result when I'm done. I'm pretty sure this script will be useful to more than just myself. ;)
EDIT 5/8/2011
I just updated the script to reflect my recent changes.
#!/usr/bin/perl
use strict; use warnings;
# local hostname
my $hostname = `/bin/hostname`;
# setup array of servers/websites to check
my #sitestocheck = ('swdlp.apple.com');
# the relative url of the website response script in each site
my $responseprogram = "/cgi-bin/WebObjects/SoftwareDownloadApp.woa/wa/getProductData?localang=en_us&grp_code=quicktime&returnURL=http://www.apple.com/quicktime/download";
# path to the log file with the response data
my $statusdir = "./tmp";
# mail feature
my $mailprog ='/usr/sbin/sendmail';
my $adminmail = 'root#localhost';
my $frommail = 'root#$hostname';
###############################################################
# End Configuration #
###############################################################
# main program
use Crypt::SSLeay;
use LWP::UserAgent;
# now check each url in your array
foreach my $sitetocheck (#sitestocheck)
{
my $ua = new LWP::UserAgent;
my $req = new HTTP::Request 'GET',"https://$sitetocheck$responseprogram";
my $res = $ua->request($req);
if ($res->is_success)
{
if ($res->content =~ m/Quicktime/i)
{
my $response = "SERVER OK:$sitetocheck:".$res->content;}
else
{
my $response = "Our apologies but there was an unexpected error with the application. This problem has been noted, and an email has been sent to the administrators. Please check back in a few hours to try the download again. ";
}
}
else
{
my $timestamp = localtime;
my $response = "WARNING! $hostname UNABLE TO CONNECT TO $sitetocheck at $timestamp";
my $traceroute = `/usr/sbin/traceroute $sitetocheck`;
}
# write server status to the main log file
open(FILE,">>$statusdir/statuslog.txt");
flock(FILE, 2);
print FILE "$response\n$traceroute\n\n";
flock(FILE, 8);
# write to a current status file for each server or website
# being monitored
open(FILE,">$statusdir/$sitetocheck");
flock(FILE, 2);
print FILE $response;
flock(FILE, 8);
}
# if there is an error mail the administrator
if (my $response =~ m/apologies/i)
{
open( MAIL, "|$mailprog -t" );
print MAIL "Subject: $hostname unable to connect to $sitetocheck\n";
print MAIL "From: $frommail\n";
print MAIL "To: $adminmail\n";
print MAIL "Reply-to: $frommail\n\n";
print MAIL "$response\n$traceroute";
print MAIL "\n\n";
close MAIL;
}
Ok, here's some observations:
Always use:
use strict;
use warnings;
Why chmod 0777? Does your logfile need to be executable?
$statusfile does not contain any data.
$traceroute contains traceroute data, but the data is then replaced with an empty string.
If no traceroute is run, you will have a print with an undefined value in the first open(), which will cause a warning in perl.
Your second open() truncates the logfile. Perhaps intentional, but worth mentioning.
The checks that are performed are rather loose. First, the only check performed on the page being valid is that it contains "Quicktime 7.6.9 for Windows XP". That could be on any page, even a page saying the system is down. Also, the $response is checked for the string "WARNING", which obviously comes from the script itself, but is checked case-insensitively, which is just strange. So, a mail is sent out not only if there is an error, but if the word "warning" appears anywhere on the download page. Not really a very good check, IMO.
The $response text says an email has been sent to the administrators, which it has not.
"/bin/hostname" the application is not used, only it's name is added to the Subject of the email. If you want it used, you need to use backticks like with traceroute (I would show you, but apparently backticks are a metacharacter in this textfield ;))
The webpage seems to come through ok, I can't test the sendmail since I am on a windows machine, but it looks ok.
It is hard to tell if this fixes your problems, since you do not specify what your problems are. It is a rather crude script, though.
I have the following script,
#!/usr/bin/perl
use strict;
use warnings;
use Net::SSH::Perl;
use Expect;
my $logs = "logs";
open(LOG,'>>',"$logs") or die "can't logs $!\n";
my $domain = 'domain.com';
my #host = qw/host/;
foreach my $host (#host) {
my $cmd = "passwd user1";
my $sshost = join('.', $host, $domain);
my $ssh = Net::SSH::Perl->new("$sshost");
$ssh->login('root');
$ssh->debug();
my ($stdout, $stderr, $exit) = $ssh->cmd($cmd);
print LOG $stdout,"\n";
}
Now my problem is I don't know how to use Expect to send the password after the $cmd is executed and it's time to key in the password. $stdin won't work in this case since we're using HPUX.
Appreciate any guidance and sample, reading the Expect docs don't result something for me.
I don't think that's possible unfortunately. However, Net::SSH::Expect seems to be able to do what you want.
I summarize: you need Expect, and the ssh module has no use.
I'll be more precise: if I understand your source code, your requirement, in human terms, is something like this: log in to a collection of Unix hosts and use passwd(1) to update root's password on each. Do I have that right?
I expect there's frustration in all directions, because variations of this question have been answered authoritatively for at least two decades. That's no reflection on you, because I recognize how difficult it is to find the correct answer.
While Net::SSH is a fine and valuable module, it contributes nothing to the solution of what I understand to be your requirements. You need Expect.
As it turns out, the standard distribution of the Tcl-based Expect includes an example which addresses your situation. Look in http://www.ibm.com/developerworks/aix/library/au-expect/ > for the description of passmass.
Identical functionality can be coded in Expect.pm, of course. Before I exhibit that, though, I ask that original questioner lupin confirm I'm on track in addressing his true requirements.
i think i had a similar issue getting into privileged exec mode with cisco routers, which similarly asks for a password when "en" is invoked. i got around it with a special subroutine:
sub enable { my ($expect_session, $password) = #_;
$expect_session->send("en\n");
$expect_session->expect($timeout,
[ qr/[Pp]assword:/,
sub {
my $expect_session = shift;
$expect_session->send("$password","\n");
exp_continue;
} ],
-re => $prompt,
);
}
but i think the issue is that you're not using Perl's Expect as it's intended to be used. An Expect session be created to manage the SSH connection, then commands are sent to through it. you don't need Net::SSH:Perl at all. here's my $expect_session definition:
my $expect_session = new Expect();
$expect_session->log_stdout(0); # let's keep things quiet on screen; we only want command output.
$expect_session->log_file(".expectlog");
$expect_session->spawn("/usr/bin/ssh -o StrictHostKeyChecking=no $username\#$host")
or die ("\nfor some reason we can't establish an SSH session to $host.\n
it's something to do with the spawn process: $!\n");
there might be a few pieces missing, but hopefully this will get you moving in the right direction. it's a complicated module which i don't understand fully. i wish you the best in luck getting it to do what you want.
Net::OpenSSH can be combined with Expect to do that easyly.
Actually the module distribution contains a sample script that does just that!
I've written a wrapper program for mailx using perl that allows me to easily add attachments and do some other nifty things that were a little frustrating to accomplish with mailx.
In the first few lines I have:
use strict;
use warnings;
use Getopt::Long;
my ( $to, $from, $subject, $attachments, $body, $file ) = (undef) x 7;
GetOptions(
"to=s" => \$to,
"from=s" => \$from,
"subject=s" => \$subject,
"attachments=s" => \$attachments,
"body=s" => \$body,
"file=s" => \$file,
);
$to = getlogin unless $to;
$from = getlogin unless $from;
$subject = " " unless $subject;
This wrapper up until now has worked fine when being called by other scripts. However now that we have a script being run by the Cron some funny things are happening. This Cron job calls the wrapper by only specifying -t and -su but omitting -fr (yes abbreviations of the flags are being used). The resulting email correctly sets the To: however has the Sender listed as -s#blah.com with the subject line blank. As per the above code I can only assume that there is something strange going between Cron and the Getopt::Long module. Does anyone know why a Cron job may cause this odd behavior? If it is something else that is wrong what would it be?
Perl's getlogin probably doesn't return anything useful from cron, quoting from getlogin(3):
getlogin() returns a pointer to a string containing
the name of the user logged in on the controlling
terminal of the process, or a null pointer if this
information cannot be determined.
I suggest changing your crontab to always include the username explicitly for any options that rely on getlogin. You could also change your wrapper to use getpwuid($<). (See perlvar(1) and perlfunc(1) for details on $< and getpwuid.)
Why that screws up your mailx, I don't know, but I'm going to guess you're using backticks, exec or system with a string to start mailx, rather than exec or system with a list.
Here's a scenario. You have a large amount of legacy scripts, all using a common library. Said scripts use the 'print' statement for diagnostic output. No changes are allowed to the scripts - they range far and wide, have their approvals, and have long since left the fruitful valleys of oversight and control.
Now a new need has arrived: logging must now be added to the library. This must be done automatically and transparently, without users of the standard library needing to change their scripts. Common library methods can simply have logging calls added to them; that's the easy part. The hard part lies in the fact that diagnostic output from these scripts were always displayed using the 'print' statement. This diagnostic output must be stored, but just as importantly, processed.
As an example of this processing, the library should only record the printed lines that contain the words 'warning', 'error', 'notice', or 'attention'. The below Extremely Trivial and Contrived Example Code (tm) would record some of said output:
sub CheckPrintOutput
{
my #output = #_; # args passed to print eventually find their way here.
foreach my $value (#output) {
Log->log($value) if $value =~ /warning|error|notice|attention/i;
}
}
(I'd like to avoid such issues as 'what should actually be logged', 'print shouldn't be used for diagnostics', 'perl sucks', or 'this example has the flaws x y and z'...this is greatly simplified for brevity and clarity. )
The basic problem comes down to capturing and processing data passed to print (or any perl builtin, along those lines of reasoning). Is it possible? Is there any way to do it cleanly? Are there any logging modules that have hooks to let you do it? Or is it something that should be avoided like the plague, and I should give up on ever capturing and processing the printed output?
Additional: This must run cross-platform - windows and *nix alike. The process of running the scripts must remain the same, as must the output from the script.
Additional additional: An interesting suggestion made in the comments of codelogic's answer:
You can subclass http://perldoc.perl.org/IO/Handle.html and create your
own file handle which will do the logging work. – Kamil Kisiel
This might do it, with two caveats:
1) I'd need a way to export this functionality to anyone who uses the common library. It would have to apply automatically to STDOUT and probably STDERR too.
2) the IO::Handle documentation says that you can't subclass it, and my attempts so far have been fruitless. Is there anything special needed to make sublclassing IO::Handle work? The standard 'use base 'IO::Handle' and then overriding the new/print methods seem to do nothing.
Final edit: Looks like IO::Handle is a dead end, but Tie::Handle may do it. Thanks for all the suggestions; they're all really good. I'm going to give the Tie::Handle route a try. If it causes problems I'll be back!
Addendum: Note that after working with this a bit, I found that Tie::Handle will work, if you don't do anything tricky. If you use any of the features of IO::Handle with your tied STDOUT or STDERR, it's basically a crapshoot to get them working reliably - I could not find a way to get the autoflush method of IO::Handle to work on my tied handle. If I enabled autoflush before I tied the handle it would work. If that works for you, the Tie::Handle route may be acceptable.
There are a number of built-ins that you can override (see perlsub). However, print is one of the built-ins that doesn't work this way. The difficulties of overriding print are detailed at this perlmonk's thread.
However, you can
Create a package
Tie a handle
Select this handle.
Now, a couple of people have given the basic framework, but it works out kind of like this:
package IO::Override;
use base qw<Tie::Handle>;
use Symbol qw<geniosym>;
sub TIEHANDLE { return bless geniosym, __PACKAGE__ }
sub PRINT {
shift;
# You can do pretty much anything you want here.
# And it's printing to what was STDOUT at the start.
#
print $OLD_STDOUT join( '', 'NOTICE: ', #_ );
}
tie *PRINTOUT, 'IO::Override';
our $OLD_STDOUT = select( *PRINTOUT );
You can override printf in the same manner:
sub PRINTF {
shift;
# You can do pretty much anything you want here.
# And it's printing to what was STDOUT at the start.
#
my $format = shift;
print $OLD_STDOUT join( '', 'NOTICE: ', sprintf( $format, #_ ));
}
See Tie::Handle for what all you can override of STDOUT's behavior.
You can use Perl's select to redirect STDOUT.
open my $fh, ">log.txt";
print "test1\n";
my $current_fh = select $fh;
print "test2\n";
select $current_fh;
print "test3\n";
The file handle could be anything, even a pipe to another process that post processes your log messages.
PerlIO::tee in the PerlIO::Util module seems to allows you to 'tee' the output of a file handle to multiple destinations (e.g. log processor and STDOUT).
Lots of choices. Use select() to change the filehandle that print defaults to. Or tie STDOUT. Or reopen it. Or apply an IO layer to it.
This isn't the answer to your issue but you should be able to adopt the logic for your own use. If not, maybe someone else will find it useful.
Catching malformed headers before they happen...
package PsychicSTDOUT;
use strict;
my $c = 0;
my $malformed_header = 0;
open(TRUE_STDOUT, '>', '/dev/stdout');
tie *STDOUT, __PACKAGE__, (*STDOUT);
sub TIEHANDLE {
my $class = shift;
my $handles = [#_];
bless $handles, $class;
return $handles;
}
sub PRINT {
my $class = shift;
if (!$c++ && #_[0] !~ /^content-type/i) {
my (undef, $file, $line) = caller;
print STDERR "Missing content-type in $file at line $line!!\n";
$malformed_header = 1;
}
return 0 if ($malformed_header);
return print TRUE_STDOUT #_;
}
1;
usage:
use PsychicSTDOUT;
print "content-type: text/html\n\n"; #try commenting out this line
print "<html>\n";
print "</html>\n";
You could run the script from a wrapper script that captures the original script's stdout and writes the output somewhere sensible.