17

How can I use bash syntax in Perl's system() command?

I have a command that is bash-specific, e.g. the following, which uses bash's process substitution:

 diff <(ls -l) <(ls -al)

I would like to call it from Perl, using

 system("diff <(ls -l) <(ls -al)")

but it gives me an error because it's using sh instead of bash to execute the command:

sh: -c: line 0: syntax error near unexpected token `('
sh: -c: line 0: `sort <(ls)'
brian d foy
  • 129,424
  • 31
  • 207
  • 592
Frank
  • 64,140
  • 93
  • 237
  • 324

5 Answers5

51

Tell Perl to invoke bash directly. Use the list variant of system() to reduce the complexity of your quoting:

my @args = ( "bash", "-c", "diff <(ls -l) <(ls -al)" );
system(@args);

You may even define a subroutine if you plan on doing this often enough:

sub system_bash {
  my @args = ( "bash", "-c", shift );
  system(@args);
}

system_bash('echo $SHELL');
system_bash('diff <(ls -l) <(ls -al)');
vladr
  • 65,483
  • 18
  • 129
  • 130
  • 3
    This also prevents you from invoking /bin/sh just to run bash – cjm Feb 20 '09 at 21:58
  • How does shift work here? Would it be the same as $_[0]? Or is it something better? – Jānis Elmeris Jul 09 '12 at 09:35
  • 1
    @Janis: The `shift` pops the first element in `@_`, namely `$_[0]`, and returns it. So the effect is the same as using `$_[0]`, plus modifying `@_`, which doesn't matter here. – musiphil Nov 25 '12 at 08:20
7
 system("bash -c 'diff <(ls -l) <(ls -al)'")

should do it, in theory. Bash's -c option allows you to pass a shell command to execute, according to the man page.

David Z
  • 128,184
  • 27
  • 255
  • 279
4

The problem with vladr's answers is that system won't capture the output to STDOUT from the command (which you would usually want), and it also doesn't allow executing more than one command (given the use of shift rather than accessing the full contents of @_).

Something like the following might be more suited to the problem:

my @cmd = ( 'diff <(ls -l) <(ls -al)', 'grep fu' );
my @stdout = exec_cmd( @cmd );
print join( "\n", @stdout );

sub exec_cmd
{
    my $cmd_str = join( ' | ', @_ );
    my @result = qx( bash -c '$cmd_str' );
    die "Failed to exec $cmd_str: $!" unless( $? == 0 && @result );
    return @result;
}

Unfortunately this won't prevent you from invoking /bin/sh just to run bash, however I don't see a workaround for this issue.

curious_prism
  • 349
  • 1
  • 5
0

I prefer to execute bash commands in perl with backticks "`". This way I get a return value, e.g.:

my $value = \`ls`;

Also, I don't have to use "bash -c" just to run a commmand. Works for me.

wattostudios
  • 8,666
  • 13
  • 43
  • 57
0

Inspired by the answer from @errant.info, I created something simpler and worked for me:

my $line="blastn -subject <(echo -e \"$seq1\") -query <(echo -e \"$seq2\") -outfmt 6";
my $result=qx(bash -c '$line');
print "$result\n";

The introduced $line variable allows modifying inputs ($seq1 and $seq2) each time. Hope it helps!

Shujun Ou
  • 31
  • 1
  • 4