Why IPC::System::Simple(capture) is not working with arguments - perl

I am trying to call a second script from the main script. When I am passing the argument in the command itself using capture, it's working. But when I am trying to send the command and arguments separately in capture function it's giving me an error that it can't find the specified file.
Second script
#!/usr/bin/perl
use 5.010;
use strict;
use warnings;
my $word= $ARGV[0];
my $crpyt = "$word crypted";
print "$crpyt\n";
my $decrypt = "$word decrypted";
print "$decrypt\n";
main.pl
#!/usr/bin/perl
use 5.010;
use strict;
use warnings;
use IPC::System::Simple qw(capture capturex);
my $cmd= 'perl xyz.pl Hello';
my #arr = capture($cmd);
print "$arr[0]";
print "$arr[1]\n";
This is working
Output:
Hello crypted
Hello decrypted
BUT
main.pl
#!/usr/bin/perl
use 5.010;
use strict;
use warnings;
use IPC::System::Simple qw(capture capturex);
my $cmd= 'perl xyz.pl';
my #arg=("Hello");
my #arr = capture($cmd,#arg);
print "$arr[0]";
print "$arr[1]\n";
This is not working. It says
"perl xyz.pl" failed to start: "The system cannot find the file specified" at main.pl line 11

If you only pass a single scalar, capture expects it to be a shell command.
As such, capture('perl xyz.pl Hello') works.
If you pass multiple scalars, capture expects the first to be a path to a program to execute. The rest are passed as arguments.
As such, capture('perl xyz.pl', 'Hello') doesn't work.
You could use
use IPC::System::Simple qw( capture );
my #cmd = ( 'perl', 'xyz.pl', 'Hello' );
capture(#cmd)
But you never want to use capture unless you pass a single scalar that's a shell command. Use capturex when passing a path and arguments.
use IPC::System::Simple qw( capturex );
my #cmd = ( 'perl', 'xyz.pl', 'Hello' );
capturex(#cmd)
But let's say you get the string perl xyz.pl from elsewhere. A shell needs to be invoked, so we need to convert the arguments in to shell literals.
use IPC::System::Simple qw( capture );
use String::ShellQuote qw( shell_quote );
my $cmd = 'perl xyz.pl';
my #extra_args = 'Hello';
my $full_cmd = $cmd . ' ' . shell_quote(#extra_args);
capture($cmd)
Alternatively,
use IPC::System::Simple qw( capturex );
my $cmd = 'perl xyz.pl';
my #extra_args = 'Hello';
capturex('sh', '-c', 'eval $0 "$#"', $cmd, #extra_args)

Related

use perl's qx{} / `...` operator with a list of arguments

system, exec, open '|-', open2, etc. all allow me to specify the command to run as a list of arguments that will be passed directly to execvp instead of run through a shell.
Even if perl is smart enough to run it directly if it looks like a "simple" command, that saves me the trouble of correctly shell-escaping the arguments with all the nasty pitfalls that it entails.
Example:
open my $out, '|-', $prog, #args;
system $prog, #args;
exec $prog, #args;
instead of
open my $out, "|$prog #args";
system "$prog #args";
exec "$prog #args";
Is there such an equivalent for the qx// operator? Or do you have to always do it by hand eg.
sub slurpcmd {
open my $h, '-|', #_ or die "open $_[0]|: $!";
local $/ unless wantarray;
<$h>
}
A list form of the qx operator is provided by the module IPC::System::Simple as the function capturex (additionally like the other functions in that module, it will throw an exception if there is an execution error or non-zero response code, which you can tweak). Alternatively, you can use Capture::Tiny to wrap a core system call and provide the same behavior, but it also has other functions that can wrap STDERR together or separately from STDOUT.
use strict;
use warnings;
use IPC::System::Simple 'capturex';
my $output = capturex $prog, #args;
use Capture::Tiny 'capture_stdout';
my ($output, $exit) = capture_stdout { system $prog, #args };
# standard system() error checking required here
In core the pipe open is for the most part the only option, aside from IPC::Open3 which is similarly complex but allows directing STDERR as well.
Here are a few simple options.
String::ShellQuote + qx:
use String::ShellQuote qw( shell_quote );
my $cmd = shell_quote(#cmd);
my $output = `$cmd`;
IPC::System::Simple:
use IPC::System::Simple qw( capturex );
my $output = capturex(#cmd)
IPC::Run3:
use IPC::Run3 qw( run3 );
run3(\#cmd, \undef, \my $output);
IPC::Run:
use IPC::Run qw( run );
run(\#cmd, \undef, \my $output);
The first solution involves a shell, but none of the others.
It turns out that (unfortunately) this wasn't an overlook from my part -- the only solution really is to do it with open -| or use one of the external modules listed in the other answers.
The backtick implementation (whether invoked by qx/.../, `...`, or readpipe) is deep down hardwired to accept a single string argument:
PP(pp_backtick)
{
dSP; dTARGET;
PerlIO *fp;
const char * const tmps = POPpconstx;
const U8 gimme = GIMME_V;
const char *mode = "r";
TAINT_PROPER("``");
if (PL_op->op_private & OPpOPEN_IN_RAW)
mode = "rb";
else if (PL_op->op_private & OPpOPEN_IN_CRLF)
mode = "rt";
fp = PerlProc_popen(tmps, mode);
...
Notice the POPpconstx which pops a single argument from the stack and the use of PerlProc_popen instead of PerlProc_popen_list.

Perl - en / em dash in command line arguments

I'm having a problem with my perl script with parsing command line arguments. Mainly, I'd like perl to parse argument preceding with (em/en)-dash as well as hypen. Consider the following command execution:
my_spript.pl -firstParam someValue –secondParam someValue2
As you can see, firstParam is prefixed with hypen and there is no problem with perl parsing it, but the secondParam is prefixed with en-dash and unfortunately Perl cannot recognize it as an argument.
I am using GetOptions() to parse arguments:
GetOptions(
"firstParam" => \$firstParam,
"secondParam" => \$secondParam
)
If you're using Getopt::Long, you can preprocess the arguments before giving them to GetOptions:
#! /usr/bin/perl
use warnings;
use strict;
use Getopt::Long;
s/^\xe2\x80\x93/-/ for #ARGV;
GetOptions('firstParam:s' => \ my $first_param,
'secondParam:s' => \ my $second_param);
print "$first_param, $second_param\n";
It might be cleaner to first decode the arguments, though:
use Encode;
$_ = decode('UTF-8', $_), s/^\N{U+2013}/-/ for #ARGV;
To work in different locale setting, use Encode::Locale:
#! /usr/bin/perl
use warnings;
use strict;
use Encode::Locale;
use Encode;
use Getopt::Long;
$_ = decode(locale => $_), s/^\N{U+2013}/-/ for #ARGV;
GetOptions('firstParam:s' => \ my $first_param,
'secondParam:s' => \ my $second_param);
print "$first_param, $second_param\n";

Pass a hash object from one perl script to another using system

I have the following perl script, that takes in a parameters' file and stores it into a hash. I want to modify & pass this hash to another perl script that I am calling using the system command:
script1.pl
#!/usr/bin/perl -w
# usage perl script1.pl script1.params
# script1.params file looks like this:
# PROJECTNAME=>project_dir
# FASTALIST=>samples_fastq.csv
use Data::Dumper;
my $paramfile = $ARGV[0];
# open parameter file
open PARAM, $paramfile or die print $!;
# save it in a hash
my %param;
while(<PARAM>)
{
chomp;
#r = split('=>');
$param{$r[0]}=$r[1];
}
# define directories
# add to parameters' hash
$param{'INDIR'} = $param{'PROJECTNAME'}.'/input';
$param{'OUTDIR'} = $param{'PROJECTNAME'}.'/output';
.... do something ...
# #samples is a list of sample names
foreach (#samples)
{
# for each sample, pass the hash values & sample name to a separate script
system('perl script2.pl <hash> $_');
}
script2.pl
#!/usr/bin/perl -w
use Data::Dumper;
## usage <script2.pl> <hash> <samplename>
# something like getting and printing the hash
my #string = $ARGV[0];
print #string;
If you can help me showing how to pass and get the hash object (something simple like printing the hash object in the second script would do), then I'd appreciate your help.
Thanks!
What you're looking for is something called serialisation. It's difficult to directly represent a memory structure in such a way as to pass it between processes, because of all sorts of fun things like pointers and buffers.
So you need to turn your hash into something simple enough to hand over in a single go.
Three key options for this in my opinion:
Storable - a perl core module that lets you freeze and thaw a data structure for this sort of purpose.
JSON - a text based representation of a hash-like structure.
XML - bit like JSON, but with slightly different strengths/weaknesses.
Which you should use depends a little on how big your data structure is.
Storable is probably the simplest, but it's not going to be particularly portable.
There's also Data::Dumper that's an option too, as it prints data structures. Generally though, I'd suggest that has all the downsides of all the above - you still need to parse it like JSON/XML but it's also not portable.
Example using Storable:
use strict;
use warnings;
use Storable qw ( freeze );
use MIME::Base64;
my %test_hash = (
"fish" => "paste",
"apples" => "pears"
);
my $frozen = encode_base64 freeze( \%test_hash );
system( "perl", "some_other_script.pl", $frozen );
Calling:
use strict;
use warnings;
use Storable qw ( thaw );
use Data::Dumper;
use MIME::Base64;
my ($imported_scalar) = #ARGV;
print $imported_scalar;
my $thing = thaw (decode_base64 $imported_scalar ) ;
print Dumper $thing;
Or:
my %param = %{ thaw (decode_base64 $imported_scalar ) };
print Dumper \%param;
This will print:
BAoIMTIzNDU2NzgEBAQIAwIAAAAKBXBhc3RlBAAAAGZpc2gKBXBlYXJzBgAAAGFwcGxlcw==
$VAR1 = {
'apples' => 'pears',
'fish' => 'paste'
};
Doing the same with JSON - which has the advantage of being passed as plain text, and in a general purpose format. (Most languages can parse JSON):
#!/usr/bin/env perl
use strict;
use warnings;
use JSON;
my %test_hash = (
"fish" => "paste",
"apples" => "pears"
);
my $json_text = encode_json ( \%test_hash );
print "Encoded: ",$json_text,"\n";
system( "perl", "some_other_script.pl", quotemeta $json_text );
Calling:
#!/usr/bin/env perl
use strict;
use warnings;
use JSON;
use Data::Dumper;
my ($imported_scalar) = #ARGV;
$imported_scalar =~ s,\\,,g;
print "Got: ",$imported_scalar,"\n";
my $thing = decode_json $imported_scalar ;
print Dumper $thing;
Need the quotemeta and the removal of slashes unfortunately, because the shell interpolates them. This is the common problem if you're trying to do this sort of thing.

call test scripts from main driver script perl

I have a main setup script which sets up the test env and stores data in some variables:
package main;
use Test::Harness;
our $foo, $xyz, $pqr;
($foo, $xyz, $pqr) = &subroutinesetup();
# ^ here
#test_files = glob "t/*";
print "Executing test #test\n";
runtests(#test_files);
In the test folder I have a testsuite (t/testsuite1.t, testsuite2.t etc.).
How can I access the value of $foo inside the testsuite1.t?
package main;
use Test::More;
$actual = getActual();
is($foo, $actual, passfoor);
# ^ here
done_testing();
Use Storable to store data in first script and retrieve it from other.
main.pl
($foo, $xyz, $pqr) = &subroutinesetup();
store ($foo, "/home/chankey/testsuite.$$") or die "could not store";
system("perl", "testsuite.pl", $$) == 0 or die "error";
testsuite.pl
my $parentpid = shift;
my $ref = retrieve("/home/chankey/testsuite.$parentpid") or die "couldn't retrieve";
print Dumper $ref;
You've received the $foo in $ref. Now use it the way you want.
You can't share a variable directly, because a new Perl process is started for each test file.
As noted in the documentation of Test::Harness, you should switch to TAP::Harness. It's more flexible: for example, it provides the test_args mechanism to pass arguments to test scripts.
$ cat 1.pl
#!/usr/bin/perl
use warnings;
use strict;
use TAP::Harness;
my $harness = 'TAP::Harness'->new({
test_args => [ qw( propagate secret ) ]
});
$harness->runtests('1.t');
__END__
$ cat 1.t
#!/usr/bin/perl
use warnings;
use strict;
use Test::More;
my %args = #ARGV;
is($args{propagate}, 'secret', 'propagated');
done_testing();

Accessing value of a global variable using require?

Is it possible to access value of a global variable declared, in another perl script using require?
eg.
Config.pl
#!/usr/bin/perl
use warnings;
use strict;
our $test = "stackoverflow"
Main.pl
#!/usr/bin/perl
use warnings;
use stricts;
require "Config.pl"
print "$test\n";
print "$config::test\n";
sure. The way you have suggested almost works. Try:
Config.pl:
use warnings;
use strict;
our $test = "stackoverflow";
and the main program:
#!/usr/bin/perl
use warnings;
use strict;
require "Config.pl";
our $test;
print "$test\n";
When you call require, the file is executed in the same namespace as the caller. So without any namespaces or my declarations any variables assigned will be globals, and will be visible to the script.
You need to declare the variable $test in Main.pl by writing
our $test;
as you do in Config.pl. Then everything will work as you expect.
Better to use a module:
MyConfig.pm: (There's a core package called "Config" already.)
package MyConfig;
use strict;
use warnings;
use Exporter qw( import );
our #EXPORT_OK = qw( $test );
our %EXPORT_TAGS = ( ALL => \#EXPORT_OK );
our $test = "stackoverflow";
1;
main.pl:
use strict;
use warnings;
use MyConfig qw( :ALL );
print "$test\n";