Perl Windows - watch a file and read last line - windows

I'm trying to watch a file in Windows Perl. I'm using Win32::ChangeNotify
Here is my code:
use strict;
use warnings;
require Win32::ChangeNotify;
use Data::Dumper;
my $Path="C:\\Eamorr\\";
my $WatchSubTree=0;
my $Events="FILE_NAME";
my $notify=Win32::ChangeNotify->new($Path,$WatchSubTree,$Events);
while(1){
$notify->reset;
$notify->wait;
print "File changed\n";
}
But "File changed" never gets printed! I realise this is quite basic stuff, but I'm really struggling on this Windows platform.
I have a file in "C:\Eamorr\Eamorr.out" which I want to monitor for changes (a new line of data is appended to this file every ten minutes by another program).
When Eamorr.out is updated, I want to be able to run some Perl and populate a MySQL table.
Please help me watching the file Eamorr.out and printing the last line to the console.
p.s. I'm on Windows Server 2003
Many thanks in advance,

This works on my Windows 7, Activestate Perl 5.16.
use feature ":5.16";
use warnings FATAL => qw(all);
use strict;
use Data::Dump qw(dump);
use Win32::ChangeNotify;
my $Path='C:\Phil\z\Perl\changeNotify\\';
my $WatchSubTree = 0;
my $Events = "SIZE";
say STDERR "Exists=", -e $Path;
my $notify=Win32::ChangeNotify->new($Path,$WatchSubTree,$Events) or say("Error=", Win32::GetLastError());
while(1)
{$notify->reset;
$notify->wait;
say STDERR "File changed";
}

Related

Call perl function from another perl script with different Active perl versions

We have two versions of Active perl 5.6 and 5.24. We have web services which has to be executed on Active perl '5.24' versions(to adopt TLS 1.2 version) and this needs to be invoked from Active perl '5.6' version. We are using windows operating system.
Steps followed :
Caller code which is executed in 5.6 version invokes the 5.24 version using system /require command.
Problem:
How to call the 5.24 perl function(example: webservicecall(arg1){return "xyz") from 5.6 perl script through system command, require or etc..?
Also how to get the return value of perl function 5.24?
Note:
Its a temporary work around to have two perl versions and the we have a plan to do upgrade it for higher version.
Here perl version 5.6 installed in "C:\Perl\bin\perl\" and perl version 5.24 installed in "D:\Perl\bin\perl\".
"**p5_6.pl**"
print "Hello Perl5_6\n";
system('D:\Perl\bin\perl D:\sample_program\p5.24.pl');
print $OUTFILE;
$retval = Mul(25, 10);
print ("Return value is $retval\n" );
"**p5_24.pl**"
print "Hello Perl5_24\n";
our $OUTFILE = "Hello test";
sub Mul($$)
{
my($a, $b ) = #_;
my $c = $a * $b;
return($c);
}
I have written sample program for detail information to call perl 5.24 version from perl script 5.6 version. During execution I didn't get the expected output. How to get the "return $c" value & the "our $OUTFILE" value of p5_24.pl in p5_6.pl script?
Note: The above is the sample program based on this I will modify the actual program using serialized data.
Place the code for the function that needs v5.24 in a wrapper script, written just so that it runs that function (and prints its result). Actually, I'd recommend writing a module with that function and then loading that module in the wrapper script.
Then run that script under the wanted (5.24) interpreter, by invoking it via its full path. (You may need to be careful to make sure that all libraries and environment are right.)   Do this in a way that allows you to pick up its output. That can be anything from backticks (qx) to pipe-open or, better, to good modules. There is a range of modules for this, like IPC::System::Simple, Capture::Tiny, IPC::Run3, or IPC::Run. Which to use would depend on how much you need out of that call.
You can't call a function in a running program but to have it somehow run under another program.
Also, variables (like $OUTFILE) defined in one program cannot be seen in another one. You can print them from the v5.24 program, along with that function result, and then parse that whole output in the v5.6 program. Then the two programs would need a little "protocol" -- to either obey an order in which things are printed, or to have prints labeled in some way.
Much better, write a module with functions and variables that need be shared. Then the v5.24 program can load the module and import the function it needs and run it, while the v5.6 program can load the same module but only to pick up that variable (and also run the v5.24 program).
Here is a sketch of all this. The package file SharedBetweenPerls.pm
package SharedBetweenPerls;
use warnings;
use strict;
use Exporter qw(import);
our #EXPORT_OK = qw(Mul export_vars);
my $OUTFILE = 'test_filename';
sub Mul { return $_[0] * $_[1] }
sub export_vars { return $OUTFILE }
1;
and then the v5.24 program (used below as program_for_5.24.pl) can do
use warnings;
use strict;
# Require this to be run by at least v5.24.0
use v5.24;
# Add path to where the module is, relative to where this script is
# In our demo it's the script's directory ($RealBin)
use FindBin qw($RealBin);
use lib $RealBin;
use SharedBetweenPerls qw(Mul);
my ($v1, $v2) = #ARGV;
print Mul($v1, $v2);
while the v5.6 program can do
use warnings;
use strict;
use feature 'say';
use FindBin qw($RealBin);
use lib $RealBin;
use SharedBetweenPerls qw(export_vars);
my $outfile = export_vars(); #--> 'test_filename'
# Replace "path-to-perl..." with an actual path to a perl
my $from_5.24 = qx(path-to-perl-5.24 program_for_5.24.pl 25 10); #--> 250
say "Got variable: $outfile, and return from function: $from_5.24";
where $outfile has the string test_filename while $from_5.24 variable is 250.†
This is tested to work as it stands if both programs, and the module, are in the same directory, with names as in this example. (And with path-to-perl-5.24 replaced with the actual path to your v5.24 executable.) If they are at different places you need to adjust paths, probably the package name and the use lib line. See lib pragma.
Please note that there are better ways to run an external program --- see the recommended modules above. All this is a crude demo since many details depend on what exactly you do.
Finally, the programs can also connect via a socket and exchange all they need but that is a bit more complex and may not be needed.
† The question's been edited, and we now have D:\Perl\bin\perl for path-to-perl-5.24 and D:\sample_program\p5.24.pl for program_for_5.24.
Note that with such a location of the p5.24.pl program you'd have to come up with a suitable location for the module and then its name would need to have (a part of) that path in it and to be loaded with such name. See for example this post.
A crude demo without a module (originally posted)
As a very crude sketch, in your program that runs under v5.6 you could do
my $from_5.24 = qx(path-to-perl-5.24 program_for_5.24.pl 25 10);
where the program_for_5.24.pl then could be something like
use warnings;
use strict;
sub Mul { return $_[0] * $_[1] }
my ($v1, $v2) = #ARGV;
print Mul($v1, $v2);
and the variable $from_5.24 ends up being 250 in my test.
You cannot directly call a Perl function running with another Perl version. You would need to create a program which explicitly invokes the function. The input and output need to be explicitly serialized in order to be transported between these two programs.
Serializing could be done with Data::Dumper, Storable or similar. If lower performance is needed you could invoke the program which provides the function with system and share the serialized data with temporary files or pipes. Or you could create some client-server architecture and share the serialized data with sockets. The latter is faster since it skips the repeated start and teardown of the other process but instead keeps it running.

From Default Perl on OSX, How To Unzip To Folder?

I'm trying to avoid having to include other libraries and keep this simple on OSX, using the default Perl that ships with OSX since Snow Leopard. I'm also trying to avoid shelling out to Bash to run the unzip. I found this example almost works, but dies on this line:
my $fh = IO::File->new($destfile, "w") or die "Couldn't write to $destfile: $!";
with this error:
Couldn't write to /tmp/mytest/Install Norton Security.localized//: Is a directory at test7.pl line 42.
Previously, I zipped the folder "Install Norton Security.localized" to mytest.zip and stuck it in /tmp. I then created a directory /tmp/mytest. Then, I ran this script with
unzip("/tmp/mytest/mytest.zip","/tmp/mytest");
I also did a lint syntax check on the script and it came back okay -- didn't warn me about missing libraries like many other zip libraries for Perl.
What do you suggest is the fix?
This routine works on older OSX and doesn't involve as many lines of code. It also handles subfolders.
#!/usr/bin/perl
use strict;
use warnings;
use Archive::Zip qw(:ERROR_CODES :CONSTANTS);
my $sSource = "/tmp/mytest.zip";
my $sDest = "/tmp/mytest";
x_unzip($sSource,$sDest);
sub x_unzip {
my ($zip_file, $out_file, $filter) = #_;
my $zip = Archive::Zip->new($zip_file);
unless ($zip->extractTree($filter || '', $out_file) == AZ_OK) {
warn "unzip not successful: $!\n";
}
}
SOURCE: https://gist.github.com/mshock/4156726

Windows process called by perl doesn't write to file properly

I'm trying to call a perl script from another perl script, read from serial port, and write to a file. I've distilled my code to isolate the problem, so it probably won't make sense what the point of the code is.
Caller:
use Win32::Process;
my $perl_path = $^X;
my $SerialLogProcess;
my $SerialLogObj;
my $serial_log_script = "callee.pl";
Win32::Process::Create($SerialLogObj, "$perl_path", "perl $serial_log_script " ,0,NORMAL_PRIORITY_CLASS,".");
$SerialLogProcess = $SerialLogObj->GetProcessID();
print "waiting for 3 secs";
sleep(3);
print "done";
`taskkill /F /T /PID $SerialLogProcess`;
Callee:
use Win32::SerialPort;
my $portObj = new Win32::SerialPort("\\\\.\\COM70") || die;
my $serialReading;
$portObj->baudrate(115200);
$portObj->parity("none");
$portObj->databits(8);
$portObj->stopbits(1);
system("rm -r \"log.txt\"");
open (LOGFILE, ">>log.txt") or die;
while (1){
$serialReading = $portObj->read(10);
print LOGFILE $serialReading;
#print LOGFILE " ";
}
So the caller creates a process for the callee script, and then kills it after 3 seconds. In those 3 seconds, I do something that gives guaranteed messages for the serial port to read. The log file is created, but nothing is written to it.
Heres what's weird: I can make the messages show up two ways. I run the callee script straight from the command line, or I can uncomment that last print. Unfortunately, these aren't solutions for me. I'm pretty stumped why I can't get my code to work properly, and it makes me think there is some kind of undefined behavior.
The output is buffered by default. The data will eventually be written once there's enough to write. Or you can use the following which will cause a flush for every print.
use IO::Handle qw( ); # Needed before Perl 5.14
open (LOGFILE, ">>log.txt") or die;
LOGFILE->autoflush(1);
But ug, why use a global variable???
open (my $LOGFILE, ">>log.txt") or die;
$LOGFILE->autoflush(1);
print $LOGFILE $serialReading;

Perl Net::SSH2 scp_put puts file and then hangs

I'm using Net::SSH2's scp_put method to place one file in my home directory on a Unix server from a Windows box. I am using Strawberry Perl 5.12 (portable version). I installed the libssh2 1.2.5 binaries and then Net::SSH2 from cpan.
Here's my code snippet:
sub uploadToHost{
my $file=#_[0];
my $host=#_[1];
my $user=#_[2];
my $pass=#_[3];
my $remotelocation=#_[4];
#makes a new SSH2 object
my $ssh=Net::SSH2->new() or die "couldn't make SSH object\n";
#prints proper error messages
$ssh->debug(1);
#nothing works unless I explicitly set blocking on
$ssh->blocking(1);
print "made SSH object\n";
#connect to host; this always works
$ssh->connect($host) or die "couldn't connect to host\n";
print "connected to host\n";
#authenticates with password
$ssh->auth_password($user, $pass) or die "couldn't authenticate $user\n";
print "authenticated $user\n";
#this is the tricky bit that hangs
$ssh->scp_put($file, $remotelocation") or die "couldn't put file in $remotelocation\n";
print "uploaded $file successfully\n";
$ssh->disconnect or die "couldn't disconnect\n";
} #ends sub
Output (edited for anonymity):
made SSH object\n
connected to host\n
authenticated \n
libssh2_scp_send_ex(ss->session, path, mode, size, mtime, atime) -> 0x377e61c\n
Net::SSH2::Channel::read(size = 1, ext = 0)\n
It then hangs forever (>40 minutes in one test) and needs to be killed.
What's strange is that it actually does scp the file to the remote server! It only hangs after it should have completed. I couldn't find references to this curious problem elsewhere on StackOverflow or elsewhere.
Can anyone point me in the right direction to either 1) stop it from hanging, or 2) implement (as a workaround) a timer that kills this one command after a few seconds, which is enough time to scp the file?
Thanks, everyone!
You can try using alarm() to prod your process into behaving, if you save this example as 'alarm.pl' you can see how it works:
use strict;
use warnings;
use 5.10.0;
# pretend to be a slow process if run as 'alarm.pl s'
if (#ARGV && $ARGV[0] eq 's') {
sleep(30);
exit();
}
# Otherwise set an alarm, then run myself with 's'
eval {
local $SIG{ALRM} = sub {die "alarmed\n"};
alarm(5);
system("perl alarm.pl s");
};
if ($#) {
die $# unless $# eq "alarmed\n";
say "Timed out slow process";
}
else {
say "Slow process finished";
}
Use Net::SFTP::Foreign with the Net::SSH2 backend, Net::SFTP::Foreign::Backend::Net_SSH2:
use Net::SFTP::Foreign;
my $sftp = Net::SFTP::Foreign->new($host, user => $user, password => $password, backend => Net_SSH2);
$sftp->die_on_error("Unable to connect to remote host");
$sftp->put($file, $remotelocation);
$sftp->die_on_error("Unable to copy file");
If that doesn't work either, you can try using plink (from the PuTTY project) instead of the Net::SSH2 backend.
I don't think it is hanging it is just REALLY SLOW. 10x slower than what it should be. The reason the file would appear to be there is that it allocates the file before it has finished transferring. This isn't really too unexpected, Perl finds new ways to disappoint and frustrate programmers on a daily basis. Sometimes I think I spend more time working around Perl's idiosyncrasies and learning 10 slightly different ways to do the same thing than doing real work.

How can I include Win32 modules only when I'm running my Perl script on Windows?

I have a problem that I cannot seem to find an answer to.
With Perl I need to use a script across Windows and unix platforms. Te problem is that on Windows we use Win32-pecific modules like Win32::Process, and those modules do not exist on unix.
I need a way to include those Win32 modules only on Windows.
if($^O =~ /win/i)
{
use win32::process qw(CREATE_NEW_CONSOLE);
}
else
{
#unix fork
}
The problem lies in that use statement for windows. No matter what I try this does not compile on unix.
I have tried using dynamic evals, requires, BEGIN, etc.
Is there a good solution to this problem? Any help will be greatly appreciated.
Thanks in advance,
Dan
Update:
A coworker pointed out to me this is the correct way to do it.
require Win32;
require Win32::Process;
my $flag = Win32::Process::CREATE_NEW_CONSOLE();
Win32::Process::Create($process,
$program,
$cmd,
0,
$flag, ".") || die ErrorReport();
print "Child started, pid = " . getPID() . "\n";
Thank you all for your help!
Dan
use is executed at compile time.
Instead do:
BEGIN {
if( $^O eq 'MSWin32' ) {
require Win32::Process;
# import Win32::Process qw(CREATE_NEW_CONSOLE);
Win32::Process->import(qw/ CREATE_NEW_CONSOLE /);
}
else {
#unix fork
}
}
See the perldoc for use.
Also see perlvar on $^O.
Update:
As Sinan Unur points out, it is best to avoid indirect object syntax.
I use direct method calls in every case, except, with calls to import. Probably because import masquerades as a built-in. Since import is really a class method, it should be called as a class method.
Thanks, Sinan.
Also, on Win32 systems, you need to be very careful that you get the capitalization of your module names correct. Incorrect capitalization means that symbols won't be imported properly. It can get ugly.use win32::process may appear to work fine.
Are you sure win32::process can be loaded on OSX? "darwin" matches your /win/i.
You may want to use http://search.cpan.org/dist/Sys-Info-Base/ which tries to do the right thing.
That aside, can you post an example of the code that you actually are using, the failure message you're receiving, and on which unix platform (uname -a) ?
What about a parser that modifies the file on each OS?
You could parse your perl file via a configure script that works on both operating systems to output perl with the proper Use clauses. You could even bury the parse action in the executable script to launch the code.
Originally I was thinking of precompiler directives from C would do the trick, but I don't know perl very well.
Here's an answer to your second set of questions:
Are you using strict and warnings?
Did you define an ErrorReport() subroutine? ErrorReport() is just an example in the synopsis for Win32::Process.
CREATE_NEW_CONSOLE is probably not numeric because it didn't import properly. Check the capitalization in your call to import.
Compare these one-liners:
C:\>perl -Mwin32::process -e "print 'CNC: '. CREATE_NEW_CONSOLE;
CNC: CREATE_NEW_CONSOLE
C:\>perl -Mwin32::process -Mstrict -e "print 'CNC: '. CREATE_NEW_CONSOLE;
Bareword "CREATE_NEW_CONSOLE" not allowed while "strict subs" in use at -e line 1.
Execution of -e aborted due to compilation errors.
C:\>perl -MWin32::Process -e "print 'CNC: '. CREATE_NEW _CONSOLE;
CNC: 16
You could just place your platform specific code inside of an eval{}, and check for an error.
BEGIN{
eval{
require Win32::Process;
Win32::Process->import(qw'CREATE_NEW_CONSOLE');
};
if( $# ){ # $# is $EVAL_ERROR
# Unix code here
}
}

Resources