change in $? after the call to close in perl - perl

Following is the code
use strict;
use warnings;
my $cmd = "ls";
my $pid = open(INPUT,"$cmd 2>&1 |");
print "PID = [$pid] [$?]\n";
if (!defined($pid)) {
print "PID not defined\n";
} else {
print "BEFORE CLOSING exit code is [$?]\n";
}
close INPUT;
print "AFTER CLOSING [$?]\n";
The output is as follows :
PID = [32300] [0]
BEFORE CLOSING exit code is [0]
AFTER CLOSING [13]
Why the value of $? changes after the call to close()?

According the documentation of perl close:
...
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}.
...
So in you example, $? contains the exit status of the command ls

perlvar says that $? is:
The status returned by the last pipe close, backtick (`` ) command, successful call to wait() or waitpid(), or from the system() operator.
So clearly it has to reflect the status of your pipe close. Before any of these operations its contents are meaningless.

Related

Perl: How to get the PID of the command executed using "make"

I have a Perl script, and in that script, I am executing the "make" command over the list of test cases. Now, I want to have the PID of each "make" command when it runs the test case so that I can monitor those PIDs.
Let's say, I have a test list
#array1 = ("/home/abc/test1/makefile", "/home/bcd/qwe/test2/makefile", "/home/PPP/makefile").
Now, when I run make on /home/abc/test1/makefile, I need the unique PID of this command (make /home/abc/test1/makefile).
Then, when I run make on /home/bcd/qwe/test2/makefile, I must get another unique ID, and similarly for other tests.
I would then use each PID to monitor for their time i.e. if a certain test executed using make (with a unique ID) reaches a time limit, then I would do something (that will be done via fork), but for that monitoring to happen, I would need the PIDs.
Code Snippet which I am using:
foreach my $i (#array1)
{
my $filehandle;
if ( ! open( $filehandle, "make $i 2>&1 |" ) ) {
die( "Failed to start process: $!" );
}
else {
print "Test started\n";
}
while ( defined( my $line = <$filehandle> ) ) {
print( $line );
}
}
How can I get the PIDs?
The pid of make is returned by open. perldoc -f open states:
Open returns nonzero on success, the undefined value otherwise.
If the "open" involved a pipe, the return value happens to be
the pid of the subprocess.
Getting the pid of processes spawned by make is a different question, and will require more effort.
open returns the PID of the process it creates. But the process being created is running sh. The PID you should receive is the PID of the shell you're launching, not that of make.[1]
If you weren't trying to execute a shell command, you could use the "list form" of open to avoid the shell. But that's not available to you since you want to use the shell to perform redirection.
There are ways to address this will still using open -|, but it's far simpler to use IPC::Run, and it handles the timeout trivially.
use IPC::Run qw( run timeout );
run [ "make", $i ],
">", \my $stdout, # Or whatever
"2>", \my $stderr, # Or whatever
timeout( 10 );
die( "make killed by signal ".( $? & 0x7F )."\n" ) if $? & 0x7F;
die( "make exited with error ".( $? >> 8 )."\n" ) if $? >> 8;
say "make successful.";
Perl sometimes optimizes the use of the shell away.

How to test the exit status from IPC::Run3

I'm trying to test the Perl module IPC::Run3 but having difficulty in checking whether a command is failed or successful.
I know that IPC::Run3 issues an exit code if something is wrong with its arguments, but what about if the arguments are ok but the command does not exist? How can I test the following example?
Having a subroutine to call Run3
sub runRun3 {
my $cmd = shift;
my ($stdout, $stderr);
run3($cmd, \undef, \$stdout, \$stderr);
# if( $? == -1 ) {
if (! $stdout and ! $stderr) {
die "Something is wrong";
} else {
print "OK \n";
}
}
when command $cmds[0] below is executed (the ls command of *nix systems) it prints OK as expected, but with command $cmds[1] it just says No such file or directory at ./testrun3.pl line 18.
With a test to the exit code I want it to print Something is wrong instead.
#!/usr/bin/perl
use warnings;
use strict;
use IPC::Run3;
my #cmds = qw(ls silly);
runRun3($cmds[0]);
runRun3($cmds[1]);
Or what would be the best alternative to IPC::Run3 in cases like this? This is just an oversimplification of the process, but eventually I would like to capture STDERR and STDOUT for more complex situations.
Thanks.
A few points to go through.
First, for the direct question, the IPC::Run3 documentation tells us that
run3 throws an exception if the wrapped system call returned -1 or anything went wrong with run3's processing of filehandles. Otherwise it returns true. It leaves $? intact for inspection of exit and wait status.
The error you ask about is of that kind and you need to eval the call to catch that exception
use warnings 'all';
use strict;
use feature 'say';
my ($stdout, $stderr);
my #cmd = ("ls", "-l");
eval { run3 \#cmd, \undef, \$stdout, \$stderr };
if ( $# ) { print "Error: $#"; }
elsif ( $? & 0x7F ) { say "Killed by signal ".( $? & 0x7F ); }
elsif ( $? >> 8 ) { say "Exited with error ".( $? >> 8 ); }
else { say "Completed successfully"; }
You can now print your own messages inside if ($#) { } block, when errors happen where the underlying system fails to execute. Such as when a non-existing program is called.
Here $# relates to eval while $? to system. So if run3 didn't have a problem and $# is false next we check the status of system itself, thus $?. From docs
Note that a true return value from run3 doesn't mean that the command
had a successful exit code. Hence you should always check $?.
For variables $# and $? see General Variables in perlvar, and system and eval pages.
A minimal version of this is to drop eval (and $# check) and expect the program to die if run3 had problems, what should be rare, and to check (and print) the value of $?.
A note on run3 interface. With \#cmd it expects #cmd to contain a command broken into words, the first element being the program and the rest arguments. There is a difference between writing a command in a string, supported by $cmd interface, and in an array. See system for explanation.
Which alternative would suit you best depends on your exact needs. Here are some options. Perhaps first try IPC::System::Simple (but no STDERR on the platter). For cleanly capturing all kinds of output Capture::Tiny is great. On the other end there is IPC::Run for far more power.

How to make perl to keep perform action until the match is found

I am new to Perl and trying to write a code to keep executing an action until the match is found and else give an error.
I am trying to execute a command ps -ef and check if it has got any process running in the name of "box", if there is no process named "box" found, I want to repeat ps -ef command execution until it gets the "box" process and then proceed to next action.
#!/usr/bin/perl -w
open (FH, "ps -ef |") or die "Cannot run the command:$!\n";
$line = "box";
while (<FH>) {
if (/$line/i) { next; }
else {
print ("ps -ef |") or die "Cannot run the command:$!\n");
}
}
close (FH);
You need to use an infinite loop and an exit-condition. Your condition is that the ps -ef command contains the word box. There is no need to open a pipe to that command explicitly, you can just run it as a system call with the qx operator (same as backticks).
use strict;
use warnings;
my $ps;
PS: while (1) {
$ps = qx/ps -ef/;
last PS if $ps =~ m/box/i;
print '.'; # do something in every run
}
print $ps;
As this has come up in the comments as well as in in AdrianHHH's answer: it might make sense to sleep after every run to make sure you don't hog the CPU. Depending on the nature of the process you are looking for, either the sleep builtin or usleep from Time::HiRes might be appropriate. The latter let's your program rest for milliseconds, while the builtin only works with full seconds. These might be too long if the target box process is very quick.
Explanation of your code:
Note that you have some issues in your implementation. I'll explain what your code does. This is taken from the question, comments are mine.
#!/usr/bin/perl -w
# open a filehandle to the ps command
open (FH, "ps -ef |") or die "Cannot run the command:$!\n";
$line = "box";
# read the output of one run line by line, for each line execute
# the block
while (<FH>) {
# if there is 'box' case-insensitive, skip the line
if (/$line/i) { next; }
else {
# else output (not run!) the command
print ("ps -ef |") or die "Cannot run the command:$!\n");
}
}
close (FH);
After it went through all the lines of the output of your command once it will stop.
I would recommend using pgrep(1) instead of ps because it lets you do a more granular search. With ps -ef, you potentially have to deal with cases like:
boxford 6254 6211 0 08:23 pts/1 00:00:00 /home/boxford/box --bounding-box=123
It's hard to tell if you're matching a process being run by a user with box in their username, a process that has box somewhere in its path, a process named box, or a process with box somewhere in its argument list.
pgrep, on the other hand, lets you match against just the process name or the full path, a specific user or users, and more. The following prints a message when a process named box appears (this looks for an exact match, so it will not match processes named dropbox, for example):
use strict;
use warnings;
use 5.010;
use String::ShellQuote qw(shell_quote);
sub is_running {
my ($proc) = #_;
my $cmd = 'pgrep -x ' . shell_quote($proc) . ' >/dev/null 2>&1';
system($cmd);
if ($? == -1) {
die "failed to execute pgrep: $!";
}
elsif ($? & 127) {
die "pgrep died with signal ", $? & 127;
}
else {
my $status = $? >> 8;
die "pgrep exited with error: exit status $status" if $status > 1;
return $status == 0;
}
}
my $proc = 'box';
until ( is_running($proc) ) {
sleep 1;
}
say "Process '$proc' is running";
Note that pgrep doesn't have a case-insensitive flag, probably because process names in *nix are almost always lowercase. If you really need to do a case-insensitive match, you can pass [Bb][Oo][Xx] to the is_running function.
The ps command outputs the current list of processes, then it completes. The code in the question reads that output. Suppose that the first ps command that is executed does not contain the wanted line, then there is nothing in the code in the question to run the ps command again.
The next statement in the question makes the script move on to the next line in the output from ps, not to rerun the command. The else print ... after the next will probably be executed for the first line of the output from ps. The outcome is that the print is run for each line in the ps output that does not have the wanted text and that the next command has no significant effect. In the code print ... or die "..." the or die "..." part is not very useful, the print is unlikely to fail and even if it did the die message would be wrong.
Perhaps you should write some code in the following style. Here the ps is run repeatedly until the wanted text is found. Note the sleep call, without that the script will keep running without pause, possibly preventing real work or at least slowing it down.
# This code is not tested.
use strict;
use warnings;
my $found_wanted_line = 0; # Boolean, set to false
my $line = "box";
while ( ! $found_wanted_line ) {
open (my $FH, "ps -ef |") or die "Cannot run the command:$!\n";
while (<$FH>) {
if (/$line/i) {
$found_wanted_line = 1; # Boolean, set to true
last;
}
}
close ($FH);
if ( ! $found_wanted_line )
sleep 2; # Pause for 2 seconds, to prevent this script hogging the CPU.
}
}

Capture the output of Perl's 'system()'

I need to run a shell command with system() in Perl. For example,
system('ls')
The system call will print to STDOUT, but I want to capture the output into a variable so that I can do future processing with my Perl code.
That's what backticks are for. From perldoc perlfaq8:
Why can't I get the output of a command with system()?
You're confusing the purpose of system() and backticks (``). system()
runs a command and returns exit status information (as a 16 bit value:
the low 7 bits are the signal the process died from, if any, and the
high 8 bits are the actual exit value). Backticks (``) run a command
and return what it sent to STDOUT.
my $exit_status = system("mail-users");
my $output_string = `ls`;
See perldoc perlop for more details.
IPC::Run is my favourite module for this kind of task. Very powerful and flexible, and also trivially simple for small cases.
use IPC::Run 'run';
run [ "command", "arguments", "here" ], ">", \my $stdout;
# Now $stdout contains output
Simply use similar to the Bash example:
$variable=`some_command some args`;
That's all. Notice, you will not see any printings to STDOUT on the output because this is redirected to a variable.
This example is unusable for a command that interact with the user, except when you have prepared answers. For that, you can use something like this using a stack of shell commands:
$variable=`cat answers.txt|some_command some args`;
Inside the answers.txt file you should prepare all answers for some_command to work properly.
I know this isn't the best way for programming :) But this is the simplest way how to achieve the goal, specially for Bash programmers.
Of course, if the output is bigger (ls with subdirectory), you shouldn't get all output at once. Read the command by the same way as you read a regular file:
open CMD,'-|','your_command some args' or die $#;
my $line;
while (defined($line=<CMD>)) {
print $line; # Or push #table,$line or do whatever what you want processing line by line
}
close CMD;
An additional extended solution for processing a long command output without extra Bash calling:
my #CommandCall=qw(find / -type d); # Some example single command
my $commandSTDOUT; # File handler
my $pid=open($commandSTDOUT),'-|'); # There will be an implicit fork!
if ($pid) {
#parent side
my $singleLine;
while(defined($singleline=<$commandSTDOUT>)) {
chomp $line; # Typically we don't need EOL
do_some_processing_with($line);
};
close $commandSTDOUT; # In this place $? will be set for capture
$exitcode=$? >> 8;
do_something_with_exit_code($exitcode);
} else {
# Child side, there you really calls a command
open STDERR, '>>&', 'STDOUT'; # Redirect stderr to stdout if needed. It works only for child - remember about fork
exec(#CommandCall); # At this point the child code is overloaded by an external command with parameters
die "Cannot call #CommandCall"; # Error procedure if the call will fail
}
If you use a procedure like that, you will capture all procedure output, and you can do everything processing line by line. Good luck :)
I wanted to run system() instead of backticks because I wanted to see the output of rsync --progress. However, I also wanted to capture the output in case something goes wrong depending on the return value. (This is for a backup script). This is what I am using now:
use File::Temp qw(tempfile);
use Term::ANSIColor qw(colored colorstrip);
sub mysystem {
my $cmd = shift; # "rsync -avz --progress -h $fullfile $copyfile";
my ($fh, $filename) = tempfile();
# http://stackoverflow.com/a/6872163/2923406
# I want to have rsync progress output on the terminal AND capture it in case of error.
# Need to use pipefail because 'tee' would be the last cmd otherwise and hence $? would be wrong.
my #cmd = ("bash", "-c", "set -o pipefail && $cmd 2>&1 | tee $filename");
my $ret = system(#cmd);
my $outerr = join('', <$fh>);
if ($ret != 0) {
logit(colored("ERROR: Could not execute command: $cmd", "red"));
logit(colored("ERROR: stdout+stderr = $outerr", "red"));
logit(colored("ERROR: \$? = $?, \$! = $!", "red"));
}
close $fh;
unlink($filename);
return $ret;
}
# And logit() is something like:
sub logit {
my $s = shift;
my ($logsec, $logmin, $loghour, $logmday, $logmon, $logyear, $logwday, $logyday, $logisdst) = localtime(time);
$logyear += 1900;
my $logtimestamp = sprintf("%4d-%02d-%02d %02d:%02d:%02d", $logyear, $logmon+1, $logmday, $loghour, $logmin, $logsec);
my $msg = "$logtimestamp $s\n";
print $msg;
open LOG, ">>$LOGFILE";
print LOG colorstrip($msg);
close LOG;
}

Perl: Capturing correct return value from 'system' command

I'm a beginner in Perl. I have a Windows batch script which contains multiple NMake commands. An existing issue with this batch script is that even if the NMake command fails during its execution, ERRORLEVEL doesn't get set properly.
So we never know whether the command worked until we parse the log file. I looked into it but couldn't find a solution. I, then thought of converting this batch script to a Perl script assuming that trapping error will be easier but it seems it's not that easy :)
Whenever I run my Perl script, the 'system' command always returns 0. I looked at many different links, and realized that capturing the correct return status of 'system' command is not that straightforward. Still, I tried the suggestions but things are not working. :(
Let me mention that the NMake command that is called, in turn, calls many different commands during its execution. For instance, the command output mentioned below, which is throwing 'fatal error', is actually part of a Perl script (check_dir.pl). This call to Perl script is written in the NMake file itself.
If I call this Perl file (check_dir.pl) directly and check for exit value, I get correct result i.e., the command fails and prints a non-zero exit value (...unexpectedly returned exit value 2).
Tried Perl's system function but it didn't help. I used the following code:
system ("nmake /f _nt.mak pack_cd SUB_PLAT=$PLAT DR=$plat 2>&1");
if ( $? == -1 ) {
print "Command failed to execute: $!\n";
}
elsif ( $? & 127 ) {
printf "The child died with signal %d, %s a coredump\n",
( $? & 127 ), ( $? & 128 ) ? 'with' : 'without';
}
else {
printf "child exited with value %d\n", $? >> 8;
}
Output:
.....
.....
Unable to open dir: R:\TSM_Latest
Compressing...NMAKE : fatal error U1077: 'if' : return code '0x2'
Stop.
child exited with value 0
Also tried:
use IPC::System::Simple qw(system);
my $exit_status = system ("nmake /f _nt.mak pack_cd SUB_PLAT=$PLAT DR=$plat 2>&1");
if ($exit_status != 0) {
print "Failure";
exit 3;
} else {
print "Success";
}
Finally tried the following module:
use IPC::Run qw( run timeout );
run "nmake /f _nt.mak pack_cd SUB_PLAT=$PLAT DR=$plat 2>&1" or die "NMake returned $?";
Nothing seems to be working :(
Please correct me if i'm interpreting the return value of system incorrectly.
You have:
use IPC::System::Simple qw(system);
my $exit_status = system ("nmake /f _nt.mak pack_cd SUB_PLAT=$PLAT DR=$plat 2>&1");
Given that you don't seem to care about the actual output, you can try
my $exit_status = systemx(nmake =>
qw(/f _nt.mak pack_cd),
"SUB_PLAT=$PLAT",
"DR=$plat",
);
To make sure you bypass cmd.exe and see if you get something useful.
For reference, the exit codes from nmake are listed here.
Running the following program:
use strict; use warnings;
use IPC::System::Simple qw(systemx);
use Try::Tiny;
my $status = 0;
try { systemx nmake => qw(/f bogus) }
catch { ($status) = ( /exit value ([0-9])/ ) };
print "Failed to execute nmake. Exit status = $status\n";
produces:
NMAKE : fatal error U1052: file 'bogus' not found
Stop.
Failed to execute nmake. Exit status = 2
The following version:
use strict; use warnings;
my $status = system nmake => qw(/f bogus);
if ($status) {
if ($? == -1) {
print "failed to execute: $!\n";
}
elsif ($? & 127) {
printf "child died with signal %d, %s coredump\n",
($? & 127), ($? & 128) ? 'with' : 'without';
}
else {
printf "child exited with value %d\n", $? >> 8;
}
}
produces:
NMAKE : fatal error U1052: file 'bogus' not found
Stop.
child exited with value 2
In fact, even when I use
my $status = system "nmake /f bogus";
I get the same correct and expected output.
Ditto when I use
my $status = system "nmake /f bogus 2>&1";
These observations lead me to the following questions:
Which version of nmake are you using?
Is the /I option in effect? Even though you don't set it from the command line, note the following:
/I Ignores exit codes from all commands. To set or clear /I for part of a makefile, use !CMDSWITCHES. To ignore exit codes for part of a makefile, use a dash (–) command modifier or .IGNORE. Overrides /K if both are specified.
So, I put together the following files:
C:\temp> cat test.mak
test.target: bogus.pl; perl bogus.pl
C:\temp> cat bogus.pl
exit 1;
And, ran:
use strict; use warnings;
my $status = system "nmake /f test.mak 2>&1";
if ($status) {
if ($? == -1) {
print "failed to execute: $!\n";
}
elsif ($? & 127) {
printf "child died with signal %d, %s coredump\n",
($? & 127), ($? & 128) ? 'with' : 'without';
}
else {
printf "child exited with value %d\n", $? >> 8;
}
}
which gave me the output:
perl bogus.pl
NMAKE : fatal error U1077: 'c:\opt\perl\bin\perl.EXE' : return code '0x1'
Stop.
child exited with value 2
where the last line shows that the exit status of nmake was correctly propagated.
Conclusion:
You have some other problem.
In fact, the OP later pointed out in comments that:
The actual command that i am trying to run is: system ("nmake /f _nt.mak pack_cd SUB_PLAT=$PLAT DR=$plat 2>&1 | C:\\tee2 $TEMP_DIR\\modules-nt_${platlogfile}");
Given tees involvement in the pipeline, it is not surprising that nmakes exit code gets lost. tee is successfully able to process output from nmake, so it returns success, and that's the exit code your script sees.
Therefore, the solution is to capture the output of nmake yourself, either using qx (coupled with the appropriate level of error checking), or using capture from IPC::System::Simple. Then, you can decide to whether you want to print that output, save to a file, put it in an email etc …