IPC::Run split parameters - perl

I use IPC::Run and I want run command (for example):
my #cmd = ("C:/test.cmd", "key=value");
IPC::Run::run \#cmd, '>', "C:\\log" or die "Failed running\n";
But realy run next command: C:/test.cmd key value
Why IPC::Run split parameter with "=" (key=value) for two parameters key and value?

From help cmd:
The special characters that require quotes are:
<space>
&()[]{}^=;!'+,`~
Use quotes:
my #cmd = ("C:/test.cmd", "\"key=value\"");

Related

Passing arguments containing spaces from one script to another in Perl

I am trying to pass arguments from one Perl script to another. Some of the arguments contain spaces.
I am reading in a comma-delimited text file and splitting each line on the comma.
my ($jockey, $racecourse, $racenum, $hnamenum, $trainer, $TDRating, $PRO) = split(/,/, $line);
The data in the comma-delimited text file look as follows:
AARON LYNCH,WARRNAMBOOL,RACE 1,DAREBIN (8),ERIC MUSGROVE,B,1
When I print out each variable, from the parent script, they look fine (as above).
print "$jockey\n";
print "$racecourse\n";
print "$racenum\n";
print "$hnamenum\n";
print "$trainer\n";
print "$TDRating\n";
print "$PRO\n";
AARON LYNCH
WARRNAMBOOL
RACE 1
DAREBIN (8)
ERIC MUSGROVE
B
1
When I pass the arguments to the child script (as follows), the arguments are passed incorrectly.
system("perl \"$bindir\\narrative4.pl\" $jockey $racecourse $racenum $hnamenum $trainer $TDRating $PRO");
AARON
LYNCH
WARRNAMBOOL
RACE
1
DAREBIN
(8)
As you can see, $ARGV[0] becomes AARON, $ARGV[1] becomes LYNCH, $ARGV[2] becomes WARRNAMBOOL, and so on.
I have investigated adding quotes to the arguments using qq, quotemeta and Win32::ShellQuote, unfortunately, even if I pass qq{"$jockey"}, the quotes are still stripped before they reach the child script, so they must be protected in some way.
I not sure if either of the aforementioned solutions is the correct but I'm happy to be corrected.
I'd appreciate any suggestions. Thanks in advance.
Note: I am running this using Strawberry Perl on a Windows 10 PC.
Note2: I purposely left out use strict; & use warnings; in these examples.
Parent Script
use Cwd;
$dir = getcwd;
$bin = "bin"; $bindir = "$dir/$bin";
$infile = "FINAL-SORTED-JOCKEY-RIDES-FILE.list";
open (INFILE, "<$infile") or die "Could not open $infile $!\n";
while (<INFILE>)
{
$line = $_;
chomp($line);
my ($jockey, $racecourse, $racenum, $hnamenum, $trainer, $TDRating, $PRO) = split(/,/, $line);
print "$jockey\n";
print "$racecourse\n";
print "$racenum\n";
print "$hnamenum\n";
print "$trainer\n";
print "$TDRating\n";
print "$PRO\n";
system("perl \"$bindir\\narrative4.pl\" $jockey $racecourse $racenum $hnamenum $trainer $TDRating $PRO");
sleep (1);
}
close INFILE;
exit;
Child Script
$passedjockey = $ARGV[0];
$passedracecourse = $ARGV[1];
$passedracenum = $ARGV[2];
$passedhnamenum = $ARGV[3];
$passedtrainer = $ARGV[4];
$passedTDRating = $ARGV[5];
$passedPRO = $ARGV[6];
print "$passedjockey\n";
print "$passedracecourse\n";
print "$passedracenum\n";
print "$passedhnamenum\n";
print "$passedtrainer\n";
print "$passedTDRating\n";
print "$passedPRO\n\n";
That whole double-quoted string that is passed to system is first evaluated and thus all variables are interpolated -- so the intended multi-word arguments become merely words in a list. So in the end the string has a command to run with individual words as arguments.
Then, even if you figure out how to stick which quotes in there just right, so to keep those multi-word arguments "together," there's still a chance of a shell being invoked, in which case those arguments again get broken up into words before being passed to the program.
Instead of all this use the LIST form of system. The first argument is then the name of the program that will be directly executed without a shell (see docs for some details on that), and the remaining arguments are passed as they are to that program.
parent
use warnings;
use strict;
use feature 'say';
my #args = ('first words', 'another', 'two more', 'final');
my $prog = 'print_args.pl';
system($prog, #args) == 0
or die "Error w/ system($prog, #args): $!";
and the invoked print_args.pl
use warnings;
use strict;
use feature 'say';
say for #ARGV;
The #ARGV contains arguments passed to the program at invocation. There's more that can be done to inspect the error, see docs and links in them.†
By what you show you indeed don't need a shell and the LIST form is generally easy to recommend as a basic way to use system, when the shell isn't needed. If you were to need shell's capabilities for something in that command then you'd have to figure out how to protect those spaces.
† And then there are modules for running external programs that are far better than system & Co. From ease-of-use to features and power:
IPC::System::Simple, Capture::Tiny, IPC::Run3, IPC::Run.

Using single quotes inside argument to recursive system bash call

I have a Perl script progA.pl which needs to run another Perl script progB.pl using the system command. However, progB.pl has been aliased in ~/.bashrc so I need to ensure that it is run after ~/.bashrc has been loaded. I can achieve this by using bash with the -lc option.
For this question, I simplify the problem as much as I think is needed, by considering the following version of progB.pl
use feature qw(say);
use strict;
use warnings;
use Data::Dump qw(dd dump);
say "Received \#ARGV: " . dump #ARGV;
and here is progA.pl:
use feature qw(say);
use strict;
use warnings;
use Data::Dump qw(dd dump);
my $cmd = qq(progB.pl --opt='This option contains '"'"'single'"'"' quotes');
say "cmd = " . dump($cmd);
system( "$cmd" );
say "-----";
system( 'bash -c ' . "$cmd" );
say "-----";
system( 'bash -c ' . "'$cmd'" );
say "-----";
system( "bash -c \"$cmd\"" );
Running
$ progA.pl
gives output:
cmd = "progB.pl --opt='This option contains '\"'\"'single'\"'\"' quotes'"
Received #ARGV: "--opt=This option contains 'single' quotes"
-----
Received #ARGV: ()
-----
Received #ARGV: "--opt=This"
-----
Received #ARGV: "--opt=This option contains single quotes"
We see that this works fine, when progB.pl is run directly without using bash -c. When I use bash -c to run the command, none of the three alternatives are working correctly.
How can I run progB.pl with an argument containing single quotes and at the same time using using bash -c ?
You should avoid this quoting madness at first place but if you insist, you should avoid at least one level of quoting by using system ARRAY version.
my $cmd = q{progB.pl --opt='This option contains '"'"'single'"'"' quotes'};
system( qw(bash -c), $cmd );
It makes it only one level of quoting madness.
my $option = q{This option contains 'single' quotes} =~ s/'/'"'"'/gr; # '
my $cmd = qq{progB.pl --opt='$option'};
system( qw(bash -c), $cmd );
There you can make some simple helper
sub sq ($) { "'" . $_[0] =~ s/'/'"'"'/gr . "'" } # "
my $option = q{This option contains 'single' quotes};
my $cmd = qq{progB.pl --opt=#{[sq $option]}};
system( qw(bash -c), $cmd );
After some trial and error, I arrived at:
use feature qw(say);
use strict;
use warnings;
my $cmd = qq(print_first_arg.pl --opt='This option contains '"'"'single'"'"' quotes');
$cmd =~ s/'/'"'"'/g;
system( 'bash -c ' . "'$cmd'" );
It seems to work, for this test case at least..
This also follows the approach suggested by #ysth in this answer:
https://stackoverflow.com/a/24869016/2173773

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;
}

How to properly escape characters for backquotes for ssh in Perl?

I have this code:
$script = "console.log(\"It works!\");";
$output = qx/ssh user#123.123.123.123 $script | interpreter/;
It's supposed to run $script through interpreter and write it into $output. The problem is that it doesn't work. How do I escape the characters correctly?
Think about what you're trying to do just with ssh. Both of these produce the same output, but work differently:
ssh user#host 'echo "puts 2+2" | ruby'
echo "puts 2+2" | ssh user#host ruby
In the first, the remote shell is executing the pipeline. (If you don't have those single quotes, what happens?) In the second, it's piped through your local shell to the ssh command and the interpreter launched.
Rather than perform convoluted escapes of code to come out correctly when crammed through sh, I prefer to pipe the text in through stdin. It's just simpler.
Using IPC::Run to do all the heavy lifting:
#!/usr/bin/env perl
use strict;
use warnings;
use IPC::Run qw(run);
my $in = <<FFFF;
2 + 2
8 * (2 + 3)
FFFF
my $cmd = [qw(ssh user#host perl calc.pl)];
run $cmd, \$in, \my $out, \my $err or die "ssh: $?";
print "out: $out";
print "err: $err";
(calc.pl is a simple infix calculator program that I had lying around)
Here's the output I get, running that:
out: 4
40
err: (SSH banners and pointless error messages)
Seeing system or qx// calls in a perl script is a sign of trouble. In general, I don't like having to think about shell syntax or shell quoting when I'm not writing shell; it has nothing to do with the problem I'm trying to solve, nor the tool I'm solving it with. (And that's ignoring any security implications.)
Oh, and if you don't have to muck with standard input but still want to execute and capture output from other programs, there's always IPC::System::Simple.
Since you're using Perl, you should do it in Perl, and not a call out to an external command.
Have you tried the Perl module Net::SSH::Perl?
I would also use qq instead of quotation marks when setting the value of $script. Using qq removes the whole how do I quote quotes mess. What ever character comes after qq is your string delimiter. All of these are valid:
my $string = qq/This is a "string" with a quote/;
my $string = qq|This is a "string" with a quote|;
my $string = qq*This is a "string" with a quote*;
Special matching quote operators are ( and ), [ and ], and { and }:
my $string = qq(This (is (a "string" with) a) quote);
Note that I can use parentheses as my string delimiters even though my string has parentheses in it. This is okay as long as those parentheses are balanced. This one wouldn't work:
my $string qq(This is an "unbalanced" parentheses ) that breaks this statement);
But, then I can switch to square brackets or curly braces:
my $string qq[This is an "unbalanced" parentheses ) but still works.];
Here's a Perl version of your program:
use strict; #Always use!
use warnings; #Always use!
use Net::SSH::Perl;
#
# Use Constants to set things that are ...well... constant
#
use constant {
HOST => "123.123.123.123",
USER => "user",
};
my $script = qq[console.log("It works!");];
my $connection = Net::SSH::Perl->new(HOST);
$connection->login(USER);
my ($output, $stderr, $exit_code) = $connection->cmd($script);
use Net::OpenSSH:
my $ssh = Net::OpenSSH->new('user#123.123.123.123');
my $output = $ssh->capture({stdin_data => 'console.log("It works!");'},
'interpreter');

Send perl variables to a command and capture the output

I'm attempting to pass perl variables into a system command and then capture the output for later usage, here's my current code:
my $updatedCmd = "|svn diff --summarize $svnOldFull $svnNewFull";
my $updatedUrls = '';
open UPDATES, $updatedCmd or die "Can't get updates";
while(<UPDATES>) {
print $_;
}
print "THIS_SHOULD_OUTPUT_AT_THE_END\n";
The problem with this is that I get the output:
THIS_SHOULD_OUTPUT_AT_THE_END
A /test
A /test2
A /deployment.txt
I would like to be able to capture all of the command output before allowing my perl script to go any further however.
More modern way to do this is the following:
my #cmd = qw(svn diff --summarize), $svnOldFull, $svnNewFull;
open my $pipe, '-|', #cmd or die "oops: $!";
while (<$pipe>) { ... }
Advantages
no globals
open mode separated from file/command
command as array, so there is no need in shell quoting
You placed the pipe on the wrong end of your command. Try this:
my $updatedCmd = "svn diff --summarize $svnOldFull $svnNewFull|";