I\'ve written some code in Perl which executes some bash command within its execution. My problem was when bash command attributes contained white space inside which failed
It's much easier to use single quotes in bash; then the only character you need to worry about is a single quote itself.
($arbitrary_string) =~ s/'/'"'"'/g;
`echo '$arbitrary_string'`
Don't use backticks to keep your string away from the shell, and hence all the quoting issues:
system 'echo', $var;
You write:
I need stdout from this command unfortunately. Other thing is that variables are used in a lil bit complicated command which uses many pipes and stuff:
echo $var | sed | grep | awk | and so on... and so on...
You might want to investigate with something like this (untested, largely cribbed from the IPC::Open3 perldoc)
use IPC::Open3;
use Symbol 'gensym';
my ($wtr, $rdr, $err);
$err = gensym;
my $pid = open3($wtr, $rdr, $err, 'sed|grep|awk|and so on');
print $wtr $var;
close $wtr;
my $output = <$rdr>;
waitpid $pid, 0;
If your pipeline is very messy, store it in a shell script file, and invoke that script from perl.
Use quotemeta:
my $arg1 = quotemeta('string to be escaped');
`echo $arg1`
Or \Q\E
(which is exactly what quotemeta is);
my $arg1 = 'string to be escaped';
`echo \Q$arg1\E`
And also please note that using echo
is a bad way to print arbitrary strings.
And do NOT place any quotes around parameters if you're using quotemeta.
CPAN to the rescue.
String::ShellQuote should do what you need, although I concur with @glennjackman that system
(or Capture::Tiny) is a better approach:
use String::ShellQuote 'shell_quote';
my $cmd = shell_quote( 'echo', 'This is sample text ending in a slash \\' );
`$cmd`;
When calling directly from perl, as Glenn Jackman says, I would use system or exec to avoid quoting trouble and to capture the output, for example to compute the md5sum of a file:
my $pid, $safe_kid;
die "cannot fork: $!" unless defined ($pid = open($safe_kid, "-|"));
if ($pid == 0) {
# This is the child process, exec md5sum
exec('/usr/bin/md5sum', '--binary', $filename) or die "can't exec md5sum: $!";
} else {
# This is the parent process, read data (we do not need to wait for
# child process termination)
@sum = <$safe_kid>;
close $safe_kid; # $? contains status
}
if ($?!=0) {
die "Problem computing hashsums on '$filename': $!\n";
}
On a related note, I was looking for a way to print command line arguments to the shell for the user to copy and paste. I hit upon the idea of single-quoting everything, and using echo
to recompose the string if a single-quote was already present, although using String::ShellQuote seems to be a better idea, really:
#!/usr/bin/perl
use strict;
testEscapeForBash("Hello World");
testEscapeForBash("Hello World!");
testEscapeForBash("Nothing_special_here.txt");
testEscapeForBash("With'One Single Quote");
testEscapeForBash("With 'Two Single' Quotes");
testEscapeForBash("'With Surrounding Single Quotes'");
testEscapeForBash("With \$ Dollar Sign");
testEscapeForBash("With * Fileglob");
testEscapeForBash("With ! History Expansion Sign");
testEscapeForBash(" ");
testEscapeForBash(" Some surrounding spaces ");
sub testEscapeForBash {
my ($in) = @_;
my $out = escapeForBash($in);
print "[$in] gives\n $out\n";
}
sub escapeForBash {
my ($name) = @_;
if (!$name) {
die "Empty name passed to 'escapeForBash'"
}
my @parts = split(/'/,$name,-1); # limit is negative to catch trailing quote
my $res;
if (@parts == 1) {
$res = "'$name'"
}
elsif (@parts > 1) {
$res = '$(echo ';
my $first = 1;
for my $part (@parts) {
$res .= "\"'\"" unless $first;
$first = 0;
$res .= "'";
$res .= $part;
$res .= "'";
}
$res .= ')';
}
else {
die "Weird number of parts: @parts"
}
return $res
}
Let's run this:
[Hello World] gives 'Hello World' [Hello World!] gives 'Hello World!' [Nothing_special_here.txt] gives 'Nothing_special_here.txt' [With'One Single Quote] gives $(echo 'With'"'"'One Single Quote') [With 'Two Single' Quotes] gives $(echo 'With '"'"'Two Single'"'"' Quotes') ['With Surrounding Single Quotes'] gives $(echo ''"'"'With Surrounding Single Quotes'"'"'') [With $ Dollar Sign] gives 'With $ Dollar Sign' [With * Fileglob] gives 'With * Fileglob' [With ! History Expansion Sign] gives 'With ! History Expansion Sign' [ ] gives ' ' [ Some surrounding spaces ] gives ' Some surrounding spaces '