Unable to inspect variable - perl

I'm having a very difficult time inspecting the $return variable. The print "return = ". $return ."\n"; always comes back blank even though the process is still running. I do receive a warning about uninitialized variable. Can someone please explain?
my $process="MInstaller";
my $return=` ps -eaf |grep $process | grep -v grep`;
sub chk_proc{
print "in chk_proc\n";
print "\n";
print "return = ". $return ."\n";
while ( my $return ne "" ) {
sleep(5);
};
};

You're close. Your code doesn't works, because the variable $return in the
while ( my $return ne "" ) {
is another variable (declared in the scope of while) as your first $return.
You can try the next:
use 5.014;
use warnings;
chk_proc('[M]Installer'); #use the [] trick to avoid the 'grep -v grep :)
sub chk_proc{ while( qx(ps -eaf |grep $_[0]) ) {sleep 5} };

Are you using use warnings; and use strict;?
What about using pgrep instead of ps?
What happens if $return returns more than one line?
Your program would flow better if your subroutine merely checked to see if the process is running and you used that in another loop.
Here, my check process subroutine returns a list of all the processes it find. I can use this in my loop to see if the process itself has stopped. I could have used qx() to get a process list, then use split to create a list of processes.
use warnings;
use strict;
use feature qw(say);
use constant {
PROCESS => "MInstaller",
SLEEP => 5,
};
while ( process_check( PROCESS ) ) {
say qq(Process ) . PROCESS . qq( is running...);
sleep SLEEP;;
}
say qq(Process ) . PROCESS . qq( has ended.);
sub process_check {
my $process = shift;
open ( my $process_fh, "-|", "pgrep $process" );
my #process_list;
while ( my $line = <$process_fh> ) {
chomp $line;
push #process_list, $line;
}
close $process_fh;
return #process_list;
}

Related

Perl: How to exit from main script if exit criteria is in a subscript?

I have a script main.pl which contains a loop over a list. This loop calls a subscript sub.pl which contains an exit criteria. When the criteria is met the program exit only the subscript but not the main script. How I have to change the code to stop the main script?
main.pl:
use strict;
use warnings;
my #list = qw (a b c);
foreach my $i (#list) {
my $cmd = "sub.pl $i";
print "$cmd\n";
system($cmd);
}
sub.pl:
use strict;
use warnings;
use Cwd;
my $dir0 = getcwd;
open my $LOG, ">>$dir0/log.txt" or die "Cannot open log.txt: $!\n";
my $value = $ARGV[0];
if ( $value eq 'b') {
print "\nExit from script 2\n";
exit;
}
print $LOG "Value is $value\n";
close $LOG;
The STDOUT is:
# sub.pl a
# sub.pl b
#
# Exit from script 2
# sub.pl c
And the $LOG output is:
# Value is a
# Value is c
I would like that the script stops at value b.
sub.pl is always exiting at the end of the script, not just when you exit explicitly exit somewhere else. You can exit with a specific code in sub.pl:
exit 1 if $value eq "b";
and then look for the exit code in main.pl:
system( $cmd ) and last
Adding to #AKHolland's answer...
In general unfortunately can't just check the exit code of system($cmd) as perl makes life difficult by multiplexing a lot more information than just the exit code into $?. [1] I would recommend always using code like this to check it.
system($cmd);
last if ( ($? >> 8) & 255);
[1] https://perldoc.perl.org/functions/system

Strange output in printing a grep -c return

Below is a shell program, it's just me testing how to tell if the same Perl script is already running using the same parameters.
It works, but what strikes me as odd is the trailing zero after the count of lines. I don't see where in my code that zero would be printed. If I run that grep command from the command line it just returns the count, no trailing '0', but it doesn't work that way in the code.
Any clues anyone?
Thanks
The App
#!/usr/bin/perl
use CGI;
$|=1;
my $q = CGI->new();
my $userid = $q->param("u");
my $check = "'perly.pl u=" . $userid . "'";
my $return= system("ps aux | grep $check | grep -v 'grep' -c");
print $return;
print "\n";
while(1)
{
print "$userid...";
sleep(3);
}
exit 0;
The output
2 0
PlasticProgrammer..PlasticProgrammer..PlasticProgrammer..PlasticProgrammer..PlasticProgrammer..PlasticProgrammer..PlasticProgrammer..PlasticProgrammer..PlasticProgrammer..PlasticProgrammer..PlasticProgrammer..
You can easily capture the output and exit value of a command using Capture::Tiny. Also, when using the built-in system, always make sure to check that the execution succeeded, if it failed this is indicated by a return value of -1.
use strict;
use warnings;
use Capture::Tiny 'capture_stdout';
my $check = "perly.pl u=foo";
my ($stdout, $exit) = capture_stdout { system 'pgrep', '-fc', $check };
die "pgrep failed: $!" if $exit == -1;
Remember that your output will most likely end in a newline, even just a count, so you probably want to chomp it.
You can also use the backticks/qx operator to return just the STDOUT, but this will always pass the command through the shell, requiring you to use String::ShellQuote on your argument containing user input as noted by #ikegami.
use strict;
use warnings;
use String::ShellQuote;
my $check = shell_quote "perly.pl u=foo";
my $stdout = `pgrep -fc $check`;
die "pgrep failed: $!" if $? == -1;
An alternative is IPC::System::Simple, which provides capturex, a form of the backticks operator that takes a list of arguments like system and never passes them through the shell. By default it will throw an exception if the command fails or if there's a nonzero exit code, but you can specify to allow certain exit codes. Since pgrep returns a nonzero exit code if there's no matches, you want to allow nonzero exit codes.
use strict;
use warnings;
use IPC::System::Simple qw(capturex EXIT_ANY $EXITVAL);
my $check = "perly.pl u=foo";
my $stdout = capturex EXIT_ANY, 'pgrep', '-fc', $check;
my $exit = $EXITVAL;
In case anyone is looking for a solution to this, good ideas up top but in the end I chose to create a unique temp file and pipe the output to that file.
Many ways, I found this the one that felt right for me.
#!/usr/bin/perl
use CGI;
use File::Temp;
$|=1;
my $q = CGI->new();
my $userid = $q->param("u");
my $check = "'perly.pl u=" . $userid . "'";
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
$year += 1900;
$mon++;
my $uFile = $userid . $year . $mon . $mday . $hour . $min . $sec . ".dat";
my $command = "ps aux | grep $check | grep -v 'grep' -c > $uFile";
system($command);
if (open(my $fh, "$uFile"))
{
while (my $line = <$fh>)
{
if($line>1)
{
print"\nApp is already running";
}
}
close($fh);
unlink($uFile);
}
exit 0;

perl array for non-zero values

Im trying to code a "service" script based on "ps".
my code:
#!/usr/bin/perl
use strict;
use warnings;
die "usage: $0 <service name>\n" unless $ARGV[0];
my $service = $ARGV[0];
open(my $ps, "ps -aux |") || die "Uknown command\n";
my #A = <$ps>;
close $ps;
foreach my $i(grep /$service/, #A){
chomp $i;
if($i=~ /root/){
next
}
print "$i\n";
}
My problem: When running the script against undef arg like:
$0 blablabla
I want to return an output if there is no such service appears/when returns 0
Thanks
I assume what you are asking is: How to give a proper message when no matching lines are found?
Well, just store the result in an array instead:
my #lines = grep { !/root/ && /$service/ } #A;
if (#lines) { # if any lines are found
for my $line (#lines) {
...
}
} else {
print "No match for '$service'!\n";
}
Or you can print the number of matches regardless of their number:
my $found = #lines;
print "Matched found: $found\n";
Note also that you can add the check for root in your grep.
As a side note, this part:
die "usage: $0 <service name>\n" unless $ARGV[0];
my $service = $ARGV[0];
Is perhaps better written
my $service = shift;
die "usage ...." unless defined $service;
Which specifically checks if the argument is defined or not, as opposed to true or not.
If I understand you correctly, you want to inform the user if no such service was found? If so, you can modify the script as follows:
my $printed; # Will be used as a flag.
foreach my $i(grep /$service/, #A){
chomp $i;
if($i=~ /root/){
next
}
$printed = print "$i\n"; # Set the flag if the service was found.
}
warn "No service found\n" unless $printed;
You can try something like this:
my #processes = grep /$service/, #A;
if ( scalar #processes ) {
foreach my $i( #processes ){
chomp $i;
if($i=~ /root/){
next;
}
print "$i\n";
}
}
else {
print 'your message';
}
You could check the result of the grep command before traversing it in the for loop, like:
...
my #services = grep { m/$service/ } #A;
# Filter the perl process running this script and...
if ( ! #services ) {
print "No service found\n";
exit 0;
}
foreach my $i( #services ){
...
}
Take into account that the grep command will never give a false return because it is including the perl process, so you will have to filter it, but I hope you get the idea.

Perl - Using backquotes missing output

Hello guys i need to capture the output of an external command, herefore I use backquotes.
However when the command reaches a newline the output is ommitted. Where $_ = AD
#lines = `"C:/Program Files/Veritas/NetBackup/bin/admincmd/bppllist" $_ -U"`
Test: test1
Test: test2
Test: test3
Test: test4
The actual output:
#lines
Test: test1
Test: test2
Thank you for your time.
print HTML "<h2 id='pol'>Policy Configuration\n</h2>" ;
#bpllist =`"$admincmd/bppllist.exe"` or die print "$admincmd/bppllist.exe not found or could not be executed";
foreach (#bpllist)
{
print HTML "<div><table class='table'>\n";
#lines = `"$admincmd/bppllist" $_ -U` or die print "$admincmd/bpplinfo $_ -U not found or could not be executed";
print HTML "\t<tr>\n\t<td><b>Policy name: <b></td><td>$_</td>\n\t</tr>\n" ;
foreach (#lines) {
chop;
($var, $value) = split(/:/,$_,2);
$var = "" if !defined($var);
$value = "" if !defined($value);
print HTML "\t<tr>\n\t<td>$var</td><td>$value</td>\n\t</tr>\n" ;
}
print HTML "</table></div>";
}
The output of #bpllist:
AD
Sharepoint
Echchange
Vmware
Here's how to capture the STDOUT & STDERR of a spawned process using backticks:
my $output = join('', `command arg1 arg2 arg3 2>&1`);
How it works has no dependence whatsoever on newlines in the output of command.
If you also need to send text to command's STDIN, then use IPC::Open3.
Cleaned your code up a bit. It works for me.
use strict;
use warnings;
use 5.10.0;
# something missing here to set up HTML file handle
# something missing here to set up $admincmd
print HTML q{<h2 id='pol'>Policy Configuration\n</h2>};
my #bpllist = `"$admincmd/bppllist.exe"`
or die "$admincmd/bppllist.exe not found or could not be executed\n";
for my $policy (#bpllist) {
print HTML q{<div><table class='table'>\n};
my #lines = `$admincmd/bpplinfo.exe $policy -U 2>&1`;
print HTML qq{\t<tr>\n\t<td><b>Policy name: <b></td><td>$policy</td>\n\t</tr>\n} ;
for my $pair (#lines) {
chomp($pair); # only remove newlines, not other characters
my ($var, $value) = split /:/, $pair, 2;
$var //= '';
$value //= '';
print HTML qq{\t<tr>\n\t<td>$var</td><td>$value</td>\n\t</tr>\n} ;
}
print HTML q{</table></div>};
}
Update 2
You appear to be doing this on windows?
I don't think the 2>&1 trick will work there.
Instead of using qx or backticks and then shell commands to redirect output, give the core module, IPC::Cmd, a try. In particular, its exportable function &run will conveniently capture both STDOUT and STDERR for you. From the synopsis:
### in list context ###
my( $success, $error_message, $full_buf, $stdout_buf, $stderr_buf ) =
run( command => $cmd, verbose => 0 );
Maybe the command send its output to stderr.
Try this:
my $output = `'command' -ARG -L 2>&1`;
regards,

Do we have an autochomp in Perl?

This is what my Perl code looks like for monitoring a Unix folder :
#!/usr/bin/perl
use strict;
use warnings;
use File::Spec::Functions;
my $date = `date`; chomp $date;
my $datef = `date +%Y%m%d%H%M.%S`; chomp $datef;
my $pwd = `pwd`; chomp $pwd;
my $cache = catfile($pwd, "cache");
my $monitor = catfile($pwd, "monme");
my $subject = '...';
my $msg = "...";
my $sendto = '...';
my $owner = '...';
sub touchandmail {
`touch $cache -t "$datef"`;
`echo "$msg" | mail -s "$subject" $owner -c $sendto`;
}
while(1) {
$date = `date`; chomp $date;
$datef = `date +%Y%m%d%H%M.%S`; chomp $datef;
if (! -e "$cache") {
touchandmail();
} elsif ("`find $monitor -newer $cache`" ne "") {
touchandmail();
}
sleep 300;
}
To do a chomp after every assignment does not look good. Is there some way to do an "autochomp"?
I am new to Perl and might not have written this code in the best way. Any suggestions for improving the code are welcome.
Don't use the shell, then.
#! /usr/bin/perl
use warnings;
use strict;
use Cwd;
use POSIX qw/ strftime /;
my $date = localtime;
my $datef = strftime "%Y%m%d%H%M.%S", localtime;
my $pwd = getcwd;
The result is slightly different: the output of the date command contains a timezone, but the value of $date above will not. If this is a problem, follow the excellent suggestion by Chas. Owens below and use strftime to get the format you want.
Your sub
sub touchandmail {
`touch $cache -t "$datef"`;
`echo "$msg" | mail -s "$subject" $owner -c $sendto`;
}
will fail silently if something goes wrong. Silent failures are nasty. Better would be code along the lines of
sub touchandmail {
system("touch", "-t", $datef, $cache) == 0
or die "$0: touch exited " . ($? >> 8);
open my $fh, "|-", "mail", "-s", $subject, $owner, "-c", $sendto
or die "$0: could not start mail: $!";
print $fh $msg
or warn "$0: print: $!";
unless (close $fh) {
if ($! == 0) {
die "$0: mail exited " . ($? >> 8);
}
else {
die "$0: close: $!";
}
}
}
Using system rather than backticks is more expressive of your intent because backticks are for capturing output. The system(LIST) form bypasses the shell and having to worry about quoting arguments.
Getting the effect of the shell pipeline echo ... | mail ... without the shell means we have to do a bit of the plumbing work ourselves, but the benefit—as with system(LIST)—is not having to worry about shell quoting. The code above uses many-argument open:
For three or more arguments if MODE is '|-', the filename is interpreted as a command to which output is to be piped, and if MODE is '-|', the filename is interpreted as a command that pipes output to us. In the two-argument (and one-argument) form, one should replace dash ('-') with the command. See Using open for IPC in perlipc for more examples of this.
The open above forks a mail process, and $fh is connected to its standard input. The parent process (the code still running touchandmail) performs the role of echo with print $fh $msg. Calling close flushes the handle's I/O buffers plus a little extra because of how we opened it:
If the filehandle came from a piped open, close returns false if one of the other syscalls involved fails or if its program exits with non-zero status. If the only problem was that the program exited non-zero, $! will be set to 0. Closing a pipe also waits for the process executing on the pipe to exit—in case you wish to look at the output of the pipe afterwards—and implicitly puts the exit status value of that command into $? and ${^CHILD_ERROR_NATIVE}.
More generally, the IO::All module does indeed provide the equivalent of an autochomp:
use IO::All;
# for getting command output:
my #date = io("date|")->chomp->slurp;
#$date[0] contains the chomped first line of the output
or more generally:
my $fh = io("file")->chomp->tie;
while (<$fh>) {
# no need to chomp here ! $_ is pre-chomped
}
Granted, for this particular case of date I would agree with the other answerers that you are probably better off using one of the DateTime modules, but if you are simply reading in a file and want all your lines to be chomped, then IO::All with the chomp and tie options applied is very convenient.
Note also that the chomp trick doesn't work when slurping the entire contents of the handle into a scalar directly (that's just the way it is implemented).
Try putting it into a function:
sub autochomp {
my $command = shift;
my $retval = `$command`;
chomp $retval;
return $retval;
}
And then call that for each command you want to execute and then chomp.
Use DateTime or other of the date modules on CPAN instead of the date utility.
For example:
use DateTime;
my $dt = DateTime->now;
print $dt->strftime('%Y%m%d%H%M.%S');
It is possible to assign and chomp in a single line using the following syntax:
chomp ( my $date = `date` );
As for speaking more Perlishly, if you find yourself repeating the same thing over and over again, roll it into a sub:
sub assign_and_chomp {
my #result;
foreach my $cmd (#_) {
chomp ( my $chomped = $cmd );
push #result, $chomped;
}
return #result;
}
my ( $date , $datef , $pwd )
= assign_and_chomp ( `date` , `date +%Y%m%d%H%M.%S` , `pwd` );