I'm writing a Perl script that makes system calls to kill running processes. For instance, I want to kill all PuTTy windows. In order to do this, I have:
system('TASKKILL /F /IM putty* /T 2>nul');
For each process that's killed, however, I get a print saying
SUCCESS: The process with PID xxxx child of PID xxxx has been terminated.
which is cluttering my CLI. What's an easy way to eliminate these prints? Also note, that I'm executing these scripts in Cygwin.
Redirect sderr->stdout->nul:
system('TASKKILL /F /IM putty* /T 1>nul 2>&1');
or just simply grab output:
my $res = `TASKKILL /F /IM putty* /T 2>nul`;
TASKKILL writes to the first file descriptor (standard output), not the second.
You want to say
system('TASKKILL /F /IM putty* /T >nul');
$exec_shell='TASKKILL /F /IM putty* /T 2>nul';
my $a = run_shell($exec_shell);
#i use this function:
sub run_shell {
my ($cmd) = #_;
use IPC::Open3 'open3';
use Carp;
use English qw(-no_match_vars);
my #args = ();
my $EMPTY = q{};
my $ret = undef;
my ( $HIS_IN, $HIS_OUT, $HIS_ERR ) = ( $EMPTY, $EMPTY, $EMPTY );
my $childpid = open3( $HIS_IN, $HIS_OUT, $HIS_ERR, $cmd, #args );
$ret = print {$HIS_IN} "stuff\n";
close $HIS_IN or croak "unable to close: $HIS_IN $ERRNO";
; # Give end of file to kid.
if ($HIS_OUT) {
my #outlines = <$HIS_OUT>; # Read till EOF.
$ret = print " STDOUT:\n", #outlines, "\n";
}
if ($HIS_ERR) {
my #errlines = <$HIS_ERR>; # XXX: block potential if massive
$ret = print " STDERR:\n", #errlines, "\n";
}
close $HIS_OUT or croak "unable to close: $HIS_OUT $ERRNO";
#close $HIS_ERR or croak "unable to close: $HIS_ERR $ERRNO";#bad..todo
waitpid $childpid, 0;
if ($CHILD_ERROR) {
$ret = print "That child exited with wait status of $CHILD_ERROR\n";
}
return 1;
}
Related
I've this subroutine whitch start a .bat script but I don't see which line do that.
sub Traitement_Proc {
foreach my $input (#_) {
my $cmd = Fonctions_communes::ResolvEnvDos($input);
my ($proc, $args);
my $chExec;
if ($cmd =~ /^\"/) {
($proc, $args) = $cmd =~ /^\"(.+?)\"(.*)/;
} else {
($proc, $args) = $cmd =~ /^([^\s]+)(.*)/;
}
$chExec = File::Spec->catfile($::G_ParamTable{$::cstExecDir}, $proc);
$chExec = File::Spec->rel2abs($chExec, File::Spec->curdir());
$chExec = "\"".$chExec."\"" . $args;
Fonctions_communes::PrintError(" PROC : "._("Execution of script")." <" . $chExec . ">");
open PROC_OUT, $chExec." 2>&1"." & if ERRORLEVEL 1 exit/b 1"." |";
while(my $output = <PROC_OUT>) {
chomp($output);
Fonctions_communes::PrintError(decode($Fonctions_communes::console_encoding,$output));
}
close(PROC_OUT);
if ($? == 1) {
Fonctions_communes::PrintError(_("The script [_1] ended in failure.",basename($chExec)).".\n");
return 0;
}
}
return 1;
}
inside $input there is the name of bat file whitch passed in argument, there is no $args in my case so the chExec variable is "C:\Users\anes.yahiaoui\Desktop\SPOOC_BD_TU_BD_XX_BD\tieme_RE_PCCA_BD_MAIN\RE_PCCA\BD\avantBDD\Gene_CSV\Proc\IMPORT_INV.bat".
when I call this function (Traitement_proc) my IMPORT_INV will start but I don't see which line do that ?
It's open executing the command. Both open(my $pipe, "shell_cmd |") and open(my $pipe, "-|", "shell_cmd") execute a shell command with the other end of the pipe in $pipe attached to its STDOUT.
For example,
use strict;
use warnings;
use feature qw( say );
use Win32::ShellQuote qw( quote_system_string );
open(my $pipe, quote_system_string(#ARGV)." |")
or die $!;
while (<$pipe>) {
chomp;
say("[$_]");
}
if (!close($pipe)) {
die("Error waiting for child to exit: $!\n") if $!;
die("Child killed by signal ".( $? & 0x7F )."\n") if $? & 0x7F;
die("Child exited with error ".( $? >> 8 )."\n") if $? >> 8;
}
say("Child completed successfully.");
>a.pl perl -le"print for 1..5"
[1]
[2]
[3]
[4]
[5]
Child completed successfully.
In spite of having correct values for all the scalar values present in the arguments, This section of code keep getting failed because of the $rc value.I am not sure how the $rc value is getting calculated here.
#args = ("$isql_exe", "-U$user", "-P$password", "-S$server",
"-D$database", "-i$tmp_sql_file", "-o$tmp_err_file");
print $log_file "Truncating stage tables\n";
$rc = 0xffff & system (#args); # <--- what this does?
if ($rc != 0) {
$rc &= 0x00ff;
print $log_file "Error Executing SQL command script $rc $?\n";
$rc = 1;
} ## end if
Please suggest something.
$rc = 0xffff & system (#args); is very wrong.
$ perl -E'say system("non-existent")'
-1
$ perl -E'say 0xFFFF & system("non-existent")'
65535
This code is far better:
system(#args);
my $rc = 0;
if ($? < 0 ) { $rc=1; print $log_file "Error Executing SQL command script: $!\n"; }
elsif ($? & 0x7F) { $rc=1; print $log_file "SQL command script killed by signal ".( $? & 0x7F )."\n"; }
elsif ($? >> 8 ) { $rc=1; print $log_file "SQL command script exited with error ".( $? >> 8 )."\n"; }
It's better because it doesn't use $rc for multiple purposes; it reports error more accurately; and it's much clearer to read.
For a $? of 65280, it will say exited with error 255. Exit codes are specific to the program giving them, and are often meaningless beyond being zero or non-zero. That's why they print error messages.
I have a perl script that runs a command via rsh and I need to get the exit status of that command on the remote server. The shell on both the local and remote servers is csh (I can't change this). To get the exit status on the remote server I am running:
my $output = `rsh myserver $command;echo $status`
The value of $output is the result of the command but the value of $status is never printed out.
I removed the rsh for testing and got the same results. Here is my test script:
#!/usr/local/bin/perl5.8
use strict;
use warnings;
my $output = `printf '';echo \$status`;
print "$command\n";
print "Returned: $output\n";
And here is the output:
printf '';echo $status
Returned:
If I copy and paste the command from the output into the command line the 0 prints out like I would expect:
:>printf '';echo $status
0
Any idea why this works via the command line but not via perl?
The back tick operator in perl uses sh (or more precisely, the default system shell, different from the default login shell) to execute the code, not csh, and $status is not a predefined shell variable in sh.
Problem 1
readpipe (aka `` aka backticks) executes its command using /bin/sh, which uses $? instead of $status.
Solution 1
Adjust the command to use csh
my $status = `/bin/csh -c 'rsh myserver $command; echo $status`;
die "Can't create child: $!\n if $? < 0;
die "Child killed by signal ".($? & 0x7F)."\n" if $? & 0x7F;
die "Child exited with exit code".($? >> 8)."\n" if $? >> 8;
die "rsh exited with exit code $status\n" if $status;
Solution 2
Adjust to a bourne shell:
my $status = `rsh myserver $command; echo $?`;
die "Can't create child: $!\n if $? < 0;
die "Child killed by signal ".($? & 0x7F)."\n" if $? & 0x7F;
die "Child exited with exit code".($? >> 8)."\n" if $? >> 8;
die "rsh exited with exit code $status\n" if $status;
Solution 3
The shell actually returns the exit code of the last command it executes, so you don't need to create a new channel to grab it.
my $output = `rsh myserver $command`;
die "Can't create child: $!\n if $? < 0;
die "Child killed by signal ".($? & 0x7F)."\n" if $? & 0x7F;
die "Child exited with exit code".($? >> 8)."\n" if $? >> 8;
print($output);
It also means you are now free to capture the remote program's output without interference.
Problem 2
The contents of $command are going to be interpolated by both the local shell and the remote shell. For example, if $command contains echo *, it will list the local files instead of the remote ones. Some escaping is needed.
Solution
use String::ShellQuote qw( shell_quote );
my $local_command = shell_quote('rsh', 'myserver', $command);
my $output = `$local_command`;
die "Can't create child: $!\n if $? < 0;
die "Child killed by signal ".($? & 0x7F)."\n" if $? & 0x7F;
die "Child exited with exit code".($? >> 8)."\n" if $? >> 8;
print($output);
I created a script in perl to run programs with a timeout. If the program being executed takes longer then the timeout than the script kills this program and returns the message "TIMEOUT".
The script worked quite well until I decided to redirect the output of the executed program.
When the stdout and stderr are being redirected, the program executed by the script is not being killed because it has a pid different than the one I got from fork.
It seems perl executes a shell that executes my program in the case of redirection.
I would like to have the output redirection but still be able to kill the program in the case of a timeout.
Any ideas on how I could do that?
A simplified code of my script is:
#!/usr/bin/perl
use strict;
use warnings;
use POSIX ":sys_wait_h";
my $timeout = 5;
my $cmd = "very_long_program 1>&2 > out.txt";
my $pid = fork();
if( $pid == 0 )
{
exec($cmd) or print STDERR "Couldn't exec '$cmd': $!";
exit(2);
}
my $time = 0;
my $kid = waitpid($pid, WNOHANG);
while ( $kid == 0 )
{
sleep(1);
$time ++;
$kid = waitpid($pid, WNOHANG);
print "Waited $time sec, result $kid\n";
if ($timeout > 0 && $time > $timeout)
{
print "TIMEOUT!\n";
#Kill process
kill 9, $pid;
exit(3);
}
}
if ( $kid == -1)
{
print "Process did not exist\n";
exit(4);
}
print "Process exited with return code $?\n";
exit($?);
Thanks for any help.
Try changing $cmd from
my $cmd = "very_long_program 1>&2 > out.txt";
to
my $cmd = "exec very_long_program 1>&2 > out.txt";
The exec will tell the shell that gets spawned by perl to replace itself with very_long_program, rather than running very_long_program as a child.
(The reason perl spawns a shell in this case is because $cmd contains the redirect characters - and perl doesn't know how to handle them itself. An alternative way of solving the problem is to do the redirection in perl itself after the fork() but prior to calling exec() - but that's slightly trickier, so try the exec workaround first!)
An alternative is to redirect STDOUT and STDERR after the fork and run the command without the redirection:
open(STDOUT, ">", "out.txt") or die "Err: $!";
open(STDERR, ">&STDOUT");
exec("very_long_command");
die "Failed to exec very_long_command: $!";
I am trying to invoke a bat file from a perl script as follows:
system("call D:/SIP/run_$file_idx.bat");
However I observe that the environment variables in the bat fail to get resolved.
If I run the bat file separately from the command prompt it works.
Does system() create a new environment and execute bat file in that?
What is that I am missing?
if (($ENV{'IPSL_RUN_FLAG'}) eq "TRUE") {
my $Book = $Excel->Workbooks->Open(
"$ENV{'IPSL_TESTCASES_PATH'}IPSLFeatureLoadRunResults.xls");
# Make transparent where the IPSL tarball is installed.
# Have ControlPanel save results here.
# You can dynamically obtain the number of worksheets, rows, and columns
# through the Excel OLE interface. Excel's Visual Basic Editor has more
# information on the Excel OLE interface. Here we just use the first
# worksheet, rows 1 through 4 and columns 1 through 3.
# select worksheet number 1 (you can also select a worksheet by name)
my $count=0;
my $Sheet = $Book->Worksheets("LOADDATA");
my $tmp=0;
foreach my $row (13..776) {
foreach my $col (17..17) {
if(($Sheet->Cells($row,$col)->{'Value'} eq "Failed") ||
($Sheet->Cells($row,$col)->{'Value'} eq "No Run") ) {
$cnt_of_current_rerun_testcases++;
foreach my $col (18..18) {
# skip empty cells
next unless defined $Sheet->Cells($row,$col)->{'Value'};
my $a = $Sheet->Cells($row,$col)->{'Value'};
my $i = index($a, 'run');
$a = substr($a, 0, $i); #remove runTest*
print OUT "\n";
if($count == 0) {
print OUT "\nREM ";
print OUT "*" x 100;
print OUT "\n";
print OUT "\ntaskkill /F /FI \"USERNAME eq %USERNAME%\" /IM ips32.exe";
print OUT "\ntaskkill /F /FI \"USERNAME eq %USERNAME%\" /IM ipsldb.exe";
print OUT "\ntaskkill /F /FI \"USERNAME eq %USERNAME%\" /IM ipsltiu.ex\n";
}
print OUT "c:\n";
print OUT "\ncd ";
$a =~ s/%I/\"%I/g;
$a=~s/H%/H%\"/g;
print OUT " $a\n";
print OUT "\n";
print OUT "CALL run_SubscribeFeatureOnHIQ.bat";
print OUT "\n";
print OUT "sleep 10\n";
print OUT "\ncd ";
print OUT " $a\n";
print OUT "\n";
print OUT "CALL ";
$i=$Sheet->Cells($row,$col)->{'Value'};
$i=~ s/%I/\"%I/g;
$i=~s/H%/H%\"/g;
print OUT $i;
#print OUT $Sheet->Cells($row,$col)->{'Value'};
print OUT "\n";
$count++;
if($count == $no_simul_tcases) {
$sleep_cnt++;
print OUT "echo Going for sleep $sleep_cnt\n";
print OUT "SLEEP 300";
print OUT "\n";
$count=0;
}
}
}
}
}
print OUT "\ntaskkill /F /FI \"USERNAME eq %USERNAME%\" /IM ips32.exe";
print OUT "\ntaskkill /F /FI \"USERNAME eq %USERNAME%\" /IM ipsldb.exe";
print OUT "\ntaskkill /F /FI \"USERNAME eq %USERNAME%\" /IM ipsltiu.ex\n";
print OUT "\nset IPSL_RUN_FLAG=TRUE";
close OUT ;
system(\"start $ENV{'IPSL_TESTCASES_PATH'}SIP\\run_$file_idx.bat\");
And the batch file is:
taskkill /F /FI "USERNAME eq %USERNAME%" /IM ips32.exe
taskkill /F /FI "USERNAME eq %USERNAME%" /IM ipsldb.exe
taskkill /F /FI "USERNAME eq %USERNAME%" /IM ipsltiu.ex
c:
cd "%IPSL_TESTCASES_PATH%"SIP\TestCASE\FEATURESINT\INT_cfSRS\INT_cfSRS_cfSERRNG\
CALL run_SubscribeFeatureOnHIQ.bat
sleep 10
cd "%IPSL_TESTCASES_PATH%"SIP\TestCASE\FEATURESINT\INT_cfSRS\INT_cfSRS_cfSERRNG\
CALL "%IPSL_TESTCASES_PATH%"SIP\TestCASE\FEATURESINT\INT_cfSRS\INT_cfSRS_cfSERRNG\runTest_SRS_INT_SERRNG.bat
Possible workaround:
By using exec and specifically writing the ENV into bat file gave me a workaround:
print OUT map { "set $_=$ENV{$_}\n" }
qw( path USERPROFILE USERNAME ProgramFiles ComSpec APPDATA );
The issue is still seen with system() though. I tried Sinan's suggestion in the answerers..
Child processes inherit the environment of their parent. The bat file should have the same environment as the Perl script. If you haven't setup the environment correctly, your bat file won't see it.
Can you expand your answer to show your test case, similar to what Sinan has done? Although you say in your comment that the code is simple, that is never true. Show us the complete Perl program you have and the complete bat file that you have. Don't make us guess or do a lot of work to figure out what you are doing.
Update: Now that you've provided some code, start cutting parts out of it to make the simplest test case that still shows the problem. When you are investigating these sorts of problems, stop working on the big script and write a little script that exercises the problem you think you're having. That way, you isolate it from everything else you might be messing up.
Update 2: Let's look more closely at your system call:
system(\"start $ENV{'IPSL_TESTCASES_PATH'}SIP\\run_$file_idx.bat\");
That should be syntax error and your script should not even be able to invoke the batch file. Try
system(qq{start $ENV{IPSL_TESTCASES_PATH}SIP\\run_$file_idx.bat\\});
Update 1: Looking at the code you posted, you are using start in the system call. That starts a new cmd.exe process and that cmd.exe will remain long after your script has terminated. However, I still do not observe behavior as you describe and I am going to look at your code dump more closely.
C:\Temp> cat bg.bat
#echo %YAEV%
#echo %*
C:\Temp> cat t.pl
#!/usr/local/bin/perl
use strict;
use warnings;
$ENV{YAEV} = 'Yet another environment variable';
system 'start bg.bat "This is a test"';
In a new cmd.exe window:
C:\Temp> t
Yet another environment variable
"This is a test"
The system command [contested: /function] will start your batch script in the default batch file context (which is the system-wide environment), if your parameter to the system call is a scalar. It is basically the same as starting the command using "cmd /c myfile.bat". If your argument to system() is an array [contested: /list], you will not have this problem. Please read this for more information.