The Perl wrapper below executes commands in parallel, saving STDOUT
and STDERR to /tmp files:
open(A,"|parallel");
for $i ("date", "ls", "pwd", "factor 17") {
print A "$i 1> '/tmp/$i.out' 2> '/tmp/$i.err'\n";
}
close(A);
How do I obtain the exit status values from the individual commands?
To get the exist status of the individual jobs, parallel would need to write the info somewhere. I don't know if it does or not. If it doesn't, you can do that yourself.
my %jobs = (
"date" => "date",
"ls" => "ls",
"pwd" => "pwd",
"factor" => "factor 17",
);
open(my $parallel, "|parallel");
for my $id (keys(%jobs)) {
print $parallel
$jobs{$id}
." 1> '/tmp/$id.out'"
." 2> '/tmp/$id.err' ; "
."echo \$?"
." > '/tmp/$id.exit'\n";
}
close($parallel);
my $exit_status = $? >> 8;
if ($exit_status >= 255) {
print("Failed\n");
} else {
printf("%d failed jobs\n", $exit_status);
}
for my $id (keys(%jobs)) {
...grab output and exit code from files...
}
Update:
I went and installed parallel.
It has an option called --joblog {file} which produces a report with exit codes. It accepts - for file name if you want it to output to STDOUT.
Note that parallel doesn't recognise abnormal death by signal, so this is not included in the --joblog report. Using the solution I posted above, a missing .exit file would indicate an abnormal death. (You must make sure it doesn't exist in the first place, though.)
Update:
#Ole Tange mentions that the limitation of --joblog {file} I mentioned above, the lack of logging of death by signal, has been addressed in version 20110722.
GNU Parallel 20110722 has exit val and signal in --joblog:
parallel --joblog /tmp/log false ::: a
cat /tmp/log
Seq Host Starttime Runtime Send Receive Exitval Signal Command
1 : 1311332758 0 0 0 1 0 false a
If you want to avoid the wrapper you could consider:
cat foo | parallel "{} >\$PARALLEL_SEQ.out 2>\$PARALLEL_SEQ.err; echo \$? >\$PARALLEL_SEQ.status"
Version 20110422 or later makes it even shorter:
cat foo | parallel "{} >{#}.out 2>{#}.err; echo \$? >{#}.status"
If your lines do no contain ' then this should work too:
cat foo | parallel "{} >'{}'.out 2>'{}'.err; echo \$? >'{}'.status"
Instead of wrapping parallel, you can use any of the tons of modules available from CPAN providing similar functionality.
For instance:
use Proc::Queue size => 10, qw(run_back);
my #pids;
for $i ("date", "ls", "pwd", "factor 17") {
push #pids, run_back {
open STDOUT, '>', '/tmp/$i.out';
open STDERR, '>', '/tmp/$i.err';
exec $i;
}
}
for (#pids) {
1 while waitfor($_, 0) <= 0;
say "process $_ exit code: ", ($? >> 8);
}
Related
I want to take decision based on result of system - external commend's output.
like: In my code I am executing one system command and if that command gives error then I have to do something else something different.
Please help me to achieve this:
This is just algorithm ....... i am not able to get value from result
$result = `dt add $dest_file -c porting`;
if($result = 'error')
{
do something 1
}
else
{
do something 2
}
please suggest me way to retrieve value in $ result
I think in this case I should not use system
We have to know what the dt command is. I'm assuming it's DITrack which behaves in a nice Unixish way which means it returns a non-zero error code when it fails.
When Perl executes a system command, it returns the exit value (sort of...) in $?. If dt is DiTrack, it's a standardly implemented such Unix command, and returns a zero exit code when it works, and returns a non-zero exit code when it doesn't work, you can use the $? to determine if it succeeded or not:
$result = qx(dt add $dest_file -c porting); # qx(...) preferred over back ticks.
if ( $? != 0 ) {
chomp $result;
say qq(Everything is okay! Program returned $result);
}
else {
die qq(Some sort of error has happened);
}
The problem is that $? returns both the exit code, and mode of failure. There's this snippet of code from the system that parses $?:
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;
}
You might consider using regular expressions:
$result = `dt add $dest_file -c porting`;
if($result =~ /error/)
{
do something 1
}
else
{
do something 2
}
One thing to keep mind is that backticks typically/potentially returns the newline character as well so you need to account for this. Using regex will allow you to get around this quickly.
Problem Statement --
I display a message to user using Perl and takes an input.On the basis of input I decide whether I need to do further processing or not.This processing takes a long time ( say 5 hour) and user run this process by logging into remote Unix/Linux system.Hence to make sure that network malfunctioning will not affect the process; I want to switch the process to background.
How can I switch such running Perl process to background?
or
Is it possible to take user input from current terminal( the terminal from where user run process as input need to be taken at very starting) if process is running into background?
OS - Linux variants
Yup, you want to daemonize your program after it finishes its interaction with the user. I would encourage you to use a module like Proc::Daemon to do the work, though: there are a bunch of subtleties in doing it correctly. The POD for Proc::Daemon gives a good description of its usage, but a simple usage can be as basic as
use Proc::Daemon;
# ... finished the interactive stuff
my $pid = Proc::Daemon::Init( { work_dir => '/var/run/my_program' })
exit 0 if ($pid == 0);
die "Error daemonizing, cannot continue: $!\n" if ($! != 0);
# ... now do the background processing
# note that STDOUT and STDERR are no longer connected to the user's terminal!
Here is a very, very simple example for my comment above...
#!/usr/bin/perl
use strict;
use warnings;
my $lcnt = 0;
if( !$ARGV[0] ) { # If no ARGS on the command line, get user input
print "How many lines do you want to print?";
chomp( $lcnt = <STDIN> );
if( $lcnt > 0 ) {
# when we are sure we have what we need
# call myself.pl and put it in the background with '&'
my $cmd = "./myself.pl ".$lcnt.' &';
system($cmd);
exit(0);
} else { die "Invalid input!\n"; }
} else { # Otherwise, lets do the processing
$lcnt = $ARGV[0];
for( my $x = 0; $x <= $lcnt; $x++ ) {
my $cmd = "echo 'Printing line: $lcnt' >> /tmp/myself.txt";
system($cmd);
sleep(1);
}
}
exit(0);
If you save this to a file called 'myself.pl' then run it. With no arguments on the command line, the script will ask you to input a number. Type in 20 and press enter. You'll see the script exit almost instantly. But if you quickly
tail -f /tmp/myself.txt
you'll see that the background process is still running, printing a new line to the file every second. Also, typing the 'ps' command on Linux systems, should show the spawned process running in the background:
jlb#linux-f7r2:~/test> ps
PID TTY TIME CMD
1243 pts/1 00:00:00 bash
4171 pts/1 00:00:00 myself.pl
4176 pts/1 00:00:00 ps
Demonise the process if the correct input:
#test input
if($inputsuccess) {
if(fork() = 0) {
#child
if(fork() = 0) {
#child
#background processing
}
} else {
wait();
}
}
I am trying to run few child processes on different platforms in parallel. Parent should only proceed further once all the child processes have completed on respective platforms.
The problem is that when I use fork and then run the ‘exec’ command in the child process, it ends almost instantly. Also, the output isn't consistent. Almost every time the log shows only one line.
-bash-2.05b$ cat Agent.SOLSPARC
caught SIGTERM signal, cleaning up
or
-bash-2.05b$ cat Agent.SOLSPARC
Host: EBSO9SPC Login: esm2
Sometimes, there are few extra lines and at last the message, 'Killed by signal 15'. The command that i use in 'exec' actually calls a script which connects to remote boxes and runs make command on them. For testing purpose, i am currently passing only one platform i.e., SOLSPARC. Also, i'm only interested in knowing whether a command finished on any given platform.
I was not sure whether I was passing all the arguments to ‘exec’ correctly so I tried different combinations (after referring different links on the Internet) but to no avail. One important observation is that when i used strace to debug this issue, the command worked fine. I saw in the perldoc that exec uses /bin/sh -c on Unix platforms, but varies on other platforms. Is it that exec and strace use different shell?
Here’s the relevant portion of my code:
sub compile {
my %child_pids;
foreach $plat (0 .. $#plat_list) {
my $pid = fork;
# Didn't check the undef condition for child
if ($plat_list[$plat] eq "SOLSPARC") {
print "\nStarted Solaris build \n";
if ($pid == 0) {
print "Inside Child Process \n\n";
exec ( "${ROOT}/${REM_EXEC} -t 1200 -c \"make LANG=en_US distclean \" -b ${ROOT} -l Agent. $plat_list[$plat]" ) or die "exec failed";
} elsif ($pid > 0) {
$child_pids{"SOLSPARC"} = $pid;
}
} else {
print "\nStarted build for other platforms \n";
if ($pid == 0) {
print "Inside Child Process \n\n";
exec ( "${ROOT}/${REM_EXEC} -t 1200 -c \"make LANG=en_GB clean \" -b ${ROOT} -l Agent. $plat_list[$plat]" ) or die "exec failed";
} elsif ($pid > 0) {
$child_pids{"$plat_list[$plat]"} = $pid;
}
}
}
my %rev_child_pids = reverse %child_pids;
while ((my $kid = waitpid -1, WNOHANG) > 0) {
if ($rev_child_pids{$kid} eq "SOLSPARC") {
print "\nChild process completed for SOLARIS platform $rev_child_pids{$kid} \n";
print "Run some other command here \n";
} else {
print "\nChild process completed for other platform $rev_child_pids{$kid} \n";
print "No more commands to run \n";
}
}
}
Any suggestions?
Try using 'system' instead of 'exec'.
system `${ROOT}/${REM_EXEC} -t 1200 -c "make LANG=en_US distclean " -b ${ROOT} -l Agent. $plat_list[$plat]`
'system' works slightly differently in relation to fork so it might solve the problem.
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 …
I'm a beginner in Perl and I have some trouble using the "system" call. Here is a little piece of code where I try to execute 2 shell commands :
# First command is :
# dot -Tpng $dottmpfile > $pngfile
# Second command is :
# rm $dottmpfile
if (!($pngfile eq "")) {
my #args = ("dot", "-Tpng", $dottmpfile, " > ", $pngfile);
system (join (' ' , #args ))
or die "system #args failed : $!";
unlink $dottmpfile;
}
EDIT : Here is my code now, and I still get an error :
system dot -Tpng toto.dot > toto.png failed : Inappropriate ioctl for device at /home/claferri/bin/fractal.pl line 79.
I've used system to produce this piece of code.
Looking at perldoc -f system, note:
If there is more than one argument in LIST, or if LIST is an array with more than one value, starts the program given by the first element of the list with arguments given by the rest of the list. If there is only one scalar argument, the argument is checked for shell metacharacters, and if there are any, the entire argument is passed to the system's command shell for parsing
You are invoking system LIST so the > ends up being passed to dot instead of being interpreted by the shell.
I would recommend that you keep using system LIST because it is a little safer than passing everything through the shell. According to the docs, you can specify the output file by using the -o option to dot, so do that.
If you really want to dot your is and cross your ts (pun not intended), then you can use:
if ( defined $pngfile and $pngfile ne '') {
my #args = (dot => '-Tpng', $dottmpfile, "-o$pngfile");
if ( system #args ) {
warn "'system #args' failed\n";
my $reason = $?;
if ( $reason == -1 ) {
die "Failed to execute: $!";
}
elsif ( $reason & 0x7f ) {
die sprintf(
'child died with signal %d, %s coredump',
($reason & 0x7f),
($reason & 0x80) ? 'with' : 'without'
);
}
else {
die sprintf('child exited with value %d', $reason >> 8);
}
}
warn "'system #args' executed successfully\n";
unlink $dottmpfile;
}
You are using > to tell the shell to redirect output to a file yet by using invoking system LIST, you are bypassing the shell. Therefore, you can use:
system ( join (' ' , #args ) );
or
system "#args";
system returns 0 on success and non-zero on "failure". It's contrary to the way most of these idioms look and a little counter-intuitive, but with system calls you should use an expression like:
system($command) and warn "system $command: failed $?\n"; # and not or
or
if (system($command) != 0) { ... handle error ... }
Is the "dot" executable in the PATH? Does it have executable permissions? Which specific error are you getting with this code?
It seems that is correct according to perldoc -f system.