1

I want to run this command saw here:

diff  <(echo "string1" ) <(echo "string2")

from perl, I tried system:

system('diff  <(echo "string1" ) <(echo "string2")');

but it's causing :

sh: 1: Syntax error: "(" unexpected

Anyone knows the trick?

Further more, how to run this kind of command safely when string1 and string2 are read from two variables which may need to be escaped?

Community
  • 1
  • 1
new_perl
  • 7,345
  • 11
  • 42
  • 72
  • 3
    Have you tried the command from the command line to make sure it's legal before trying to implement it in perl? – RonaldBarzell Dec 27 '12 at 13:56
  • @RonaldBarzell , yes, I tried,it's legal.Doesn't it work for you? – new_perl Dec 27 '12 at 13:58
  • 1
    Ok, in that case, please see: http://stackoverflow.com/questions/571368/how-can-i-use-bash-syntax-in-perls-system – RonaldBarzell Dec 27 '12 at 13:59
  • Or maybe the shell you're trying to run it with just doesn't support that syntax? It looks like bash-ism or zsh-ism, and if you're using e.g. dash you'll run into problems (I'm thinking of running from cron, running CGI instead of on normal user account here). – Moritz Bunkus Dec 27 '12 at 13:59
  • @RonaldBarzell ,thanks, it's almost there, but how to deal with escaping elegantly in this case? – new_perl Dec 27 '12 at 14:01
  • Well, I would just escape each variable separately and then build the string. However, if you post a small, relevant snippet of code showing the context, more help may be possible. – RonaldBarzell Dec 27 '12 at 14:03
  • @RonaldBarzell, the above is exactly what I'm doing, but replacing `string1` and `string2` with variables. – new_perl Dec 27 '12 at 14:05
  • @new_perl Got it. In that case, for string escaping for command lines, please see: http://stackoverflow.com/questions/6306386/how-can-i-escape-an-arbitrary-string-for-use-as-a-command-line-argument-in-bash – RonaldBarzell Dec 27 '12 at 14:10
  • @RonaldBarzell, this won't work, see this one:http://stackoverflow.com/a/3301363/807893 – new_perl Dec 27 '12 at 14:51

3 Answers3

4

Use bash to execute the command

system('/bin/bash -c \'diff  <(echo "string1" ) <(echo "string2")\'');

By default, system will use /bin/sh to execute the command, and /bin/sh doesn't support feature '()'

For safe-run, need to escape key chars, eg. the quote here.

Bug Code:

my $var1 = "\"string1;ls";
my $var2 = "\"string2;ls";
my $cmd = "diff <(echo \"$var1\") <(echo \"$var2\" )";
print $cmd . "\n";
my @args = ("bash", "-c", $cmd);
system(@args);
print "=======" . "\n";

Safe Code:

$var1 = "\"string1;ls";
$var2 = "\"string2;ls";
$var1 =~ s/(\")/\\$1/g;
$var2 =~ s/(\")/\\$1/g;
$cmd = "diff <(echo \"$var1\") <(echo \"$var2\" )";
print $cmd . "\n";
@args = ("bash", "-c", $cmd);
system(@args);
Amirshk
  • 8,170
  • 2
  • 35
  • 64
melvinto
  • 369
  • 2
  • 4
1

I'm fixing this even though it doesn't handle the spaces (like you wanted), it wouldn't quote the first character. I made it smarter, so it doesn't quote the obvious stuff.

# this will escape all the characters in the string  
my $str = '/?\abc"';
( my $str3 = $str) =~ s/([^a-zA-Z0-9])/\\\1/g;

printf ("%s\n",$str);
printf ("%s\n",$str3);

my @args = ( "bash", "-c", 'diff <(echo "$foo" ) <(echo "string2")' );

system(@args);
kdubs
  • 1,596
  • 1
  • 21
  • 36
  • ah. you want to handle echo $STR1 where STR1 has odd characters in it? – kdubs Dec 27 '12 at 14:44
  • `my @args = ( "bash", "-c", 'diff <(echo "$foo" ) <(echo "string2")' );` – kdubs Dec 27 '12 at 15:01
  • seems to work. I put some odd stuff into foo. you'll just have to get the odd stuff into "foo", but I may not really understand the request. – kdubs Dec 27 '12 at 15:03
  • thanks for the input, but I'm looking for a general way to escape the string that'll work in all cases.. – new_perl Dec 27 '12 at 15:13
0

The shell-free (but possibly less-portable) version:

#!perl
use strict;
use warnings;
use Fcntl;

open my $one, '-|', 'echo', 'string1' or die "$! opening first input";
fcntl $one, F_SETFD, (fcntl($one, F_GETFD, 0) & ~FD_CLOEXEC)
  or die "$! keeping open first input";
open my $two, '-|', 'echo', 'string2' or die "$! opening second input";
fcntl $two, F_SETFD, (fcntl($two, F_GETFD, 0) & ~FD_CLOEXEC)
  or die "$! keeping open second input";

open my $diff, '-|', 'diff', "/dev/fd/".fileno($one), "/dev/fd/".fileno($two)
  or die "$! running diff";

while (<$diff>) {
    print;
}

This is basically exactly what the shell does (open a subprocess for the first "echo" command, open a subprocess for the second "echo" command, allow their filehandles to be inherited by child processes, then open a subprocess to the "diff" command, with its arguments being special filenames that point to the opened filehandles). The only difference is that instead of allowing diff to print to the screen, we capture the output and print it ourselves — because I thought you might want to do something with the output rather than merely see it.

New version (no /bin/echo, no escaping, no deadlocks)

#!/usr/bin/perl
use strict;
use warnings;
use Fcntl;

my $pid_one = open my $one, '-|';
die "$! opening first input" unless defined $pid_one;
if (!$pid_one) { # child
    print "string1\n";
    exit;
}
fcntl $one, F_SETFD, (fcntl($one, F_GETFD, 0) & ~FD_CLOEXEC)
  or die "$! keeping open first input";

my $pid_two = open my $two, '-|';
die "$! opening second input" unless defined $pid_two;
if (!$pid_two) { # child
    print "string2\n";
    exit;
}
fcntl $two, F_SETFD, (fcntl($one, F_GETFD, 0) & ~FD_CLOEXEC)
   or die "$! keeping open second input";

 open my $diff, '-|', 'diff', "/dev/fd/".fileno($one), "/dev/fd/".fileno($two)
  or die "$! running diff";
 while (<$diff>) {
    print;
}

string1 and string2 can be anything, including variables from the parent process or the output of other commands.

hobbs
  • 223,387
  • 19
  • 210
  • 288
  • Is there a general solution to escape `string1` and `string2` like in my example? – new_perl Dec 27 '12 at 15:46
  • @new_perl there's no need to escape anything here, because there's no shell to escape arguments *for*. `string1` and `string2` can be anything and they'll be passed through (unless, perhaps, they are exactly `-e`, `-n`, or `--help`, thanks to `/bin/echo`). – hobbs Dec 27 '12 at 15:54
  • @new_perl new version doesn't even use `/bin/echo`, so it's 100% clean. – hobbs Dec 27 '12 at 16:04