Reputation: 975
In Perl, the default shell to execute backticks is sh. I'd like to switch to use bash for its richer syntax. So far I found that the suggested solution is
`bash -c \"echo a b\"`
The apparent drawback is that the escaped double quotes, which means I will have difficulty to use double quotes in my bash args. For example, if I wanted to run commands requiring double quotes in bash
echo "a'b"
The above method will be very awkward.
Perl's system() call has a solution for this problem: to use ARRAY args,
system("bash", "-c", qq(echo "a'b"));
This keeps my original bash command unmodified, and almost always.
I'd like to use ARRAY args in backticks too. Is it possible?
Upvotes: 2
Views: 843
Reputation: 161
How about using a temporary file for the entire script if it is allowed in terms of security etc.?
For example, I needed to run jq
with a filter script and data for the following perl code. The code is like this. Yes, we can use perl binding of JQ, but this is just an example..
use File::Temp qw(tempfile);
my $BashCode = qq(jq -r '$JQCode' <<< '$JSON');
# create a temp script file
my ($fh, $filename) = tempfile();
print $fh $BashCode;
close $fh;
# run the script
$out = `bash $filename`;
As you know, <<<
causes an error when this command line is run with sh
(with backticks). And bash -c
didn't easily work for this case. So, this was the solution.
You can switch to use bash for its richer syntax without additional escaping.
Upvotes: 0
Reputation: 66954
For one, one can submit a list to qx
; it gets interpolated into a string and then passed to either execvp
or a shell (see qx, and the second part of this post and comments). And if you need a shell then presumably that string contains shell metacharacters so it goes via shell.
my @cmd = qw(ls -l "dir with spaces");
#my @cmd = qw(ls -l "dir with spaces" > outfile);
my @out = qx(@cmd);
print for @out;
I make a "dir with spaces"
directory with a file in it to test. (For a command with quotes in it a shell does get used.)
Next, I would in principle recommend a module to compose those shell commands, instead of going through a nail-biter to correctly escape and pass it all, like String::ShellQuote
use String::ShellQuote qw(shell_quote);
my @cmd = ('ls', '-l', q(dir with spaces));
my $quoted = shell_quote(@cmd);;
my @out = qx($quoted);
#my @out = qx($quoted > outfile);
print for @out;
I use the q(...)
operator form of single quotes to demonstrate another way (also useful for including single quotes); it is not necessary for this simple example. One still need be careful with details; that's in the nature of using complex external commands and cannot be fully avoided by any approach or tool.
As for running bash
, note that normally sh
delegates to a default-of-sorts shell on your system, and on many systems it is bash
that is used. But if it isn't on yours, one way to use bash -c
in the command would be to first prepare the command and then add that to the qx
string
my @cmd = ('ls', '-l', q(dir with spaces));
my $quoted = shell_quote(@cmd);
my @out = qx(bash -c "$quoted");
#my @out = qx(bash -c "$quoted" > outfile);
print for @out;
A couple more notes I'd like to offer:
That qx
is an ancient demon. How about using modern tools/modules for running external commands? There may be a little more to do in order to prepare your involved bash strings but then everything else will be better. Options abound. For example
IPC::System::Simple with its few utility functions
Use Capture::Tiny to wrap a system
call with syntax you prefer
The IPC::Run can do any and all of this and then way way more
Why use external commands, with Perl's (far) superior richness? It's a whole, very complete, programming language, vs. the command-interpreter with some programming capabilities. If you need shell's capabilities why not run just those things via the shell and do all else in Perl?
Upvotes: 4
Reputation: 386541
Given
my @cmd = ( "bash", "-c", qq(echo "a'b") );
You can use any of the following:
use Shell::Quote qw( shell_quote );
my $cmd = shell_quote( @cmd );
my $output = `$cmd`;
die "Can't spawn child: $!\n" if $? == -1;
die "Child killed by signal ".( $? & 0x7F )."\n" if $? & 0x7F;
die "Child exited with error ".( $? >> 8 )."\n" if $? >> 8;
or
use IPC::System::Simple qw( capturex );
my $output = capturex( @cmd );
or
use IPC::Run qw( run );
run \@cmd, '>', \my $output;
die "Child killed by signal ".( $? & 0x7F )."\n" if $? & 0x7F;
die "Child exited with error ".( $? >> 8 )."\n" if $? >> 8;
Upvotes: 2
Reputation: 240512
Capture::Tiny is a very nice option: as the SYNOPSIS shows, you can do
use Capture::Tiny 'capture';
my ($output, $error_output, $exit_code) = capture {
system(@whatever);
};
as well as using system inside capture_stdout
if you want the simpler behavior of backticks.
Plus it's very general-purpose, working on Perl code (even Perl code that does weird stuff) as well as external programs, so it's a good thing to have in your toolbox.
Upvotes: 2
Reputation: 975
I have the following sub that works
sub bash_output {
my ($cmd) = @_;
open my $ifh, "-|", "bash", "-c", $cmd or die "cannot open file handler: $!";
my $output = "";
while (<$ifh>) {
$output .= $_;
}
close $ifh;
return $output;
}
print "test bash_output()\n";
my @strings = (
qq(echo "a'b"),
'echo $BASH_VERSION',
'[[ "abcde" =~ bcd ]] && echo matched',
'i=1; ((i++)); echo $i',
);
for my $s (@strings) {
print "bash_output($s) = ", bash_output($s), "\n";
}
The output is
bash_output(echo "a'b") = a'b
bash_output(echo $BASH_VERSION) = 4.4.20(1)-release
bash_output([[ "abcde" =~ bcd ]] && echo matched) = matched
bash_output(i=1; ((i++)); echo $i) = 2
My answer is long-winded but it fills my need. I was hoping Perl has a built-in solution just like how it handles system() call and I am still hoping.
Upvotes: 2
Reputation: 40778
You can use single quotes as delimiter with qx
like this:
my $out = qx'bash -c "echo a b"';
this will according to perlop protect the command from Perl's double-quote interpolation.
Unfortunately, this does not work for single quotes. If you want to do echo "'"
for example, you need the following:
my $out = `bash -c \"echo \\\"'\\\"\"`;
Edit:
To help you managing the escaping of quotes you could use a helper function like this:
use experimental qw(signatures);
sub bash_backticks($code) {
$code =~ s/'/'"'"'/g;
`bash -c '$code'`
}
Upvotes: 0
Reputation: 26727
You can change the shell used by perl :
$ENV{PERL5SHELL} = "bash";
my $out = qx{echo "Hello ' world from bash \$BASH_VERSION"};
print($out);
Upvotes: 0