1

I'm trying to use IPC::Open::open3() (well, really IPC::Run::run() but that calls open3())...and it has an odd behavior where if I pass a command line option with quotes, open3() will add additional quotes around that option (sometimes escaped).

For example: I'm trying to run ls --color="auto" and my output is:

ls: unsupported --color value '"auto"' (must be always, auto, or never)

Here's the code:

package test;
use strict;
use warnings FATAL => 'all';

use IPC::Open3;
use POSIX;
use Symbol qw/ gensym /;

my @cmd = ('ls', '--color="auto"');

my $child_stdout;
my $child_stderr = gensym;

my $child_pid = IPC::Open3::open3(undef, $child_stdout, $child_stderr, @cmd);
my @out = <$child_stdout>;
my @err = <$child_stderr>;
close($child_stdout);
close($child_stderr);
waitpid($child_pid, POSIX::WNOHANG);

print join("", @out);
print join("", @err);
1;

NOTE: I realize I can run ls --color=auto...but there's another command I'm running that accepts strings with spaces in them which require quotes, ls is just a reproducible example.

I tried figuring out where in the source that IPC::Open3 is adding the quotes but I'm failing to where it's happening.

Long story short:

  • Does anyone know how to stop the behavior of IPC::Open3 adding quotes to command arguments?
  • Is there another IPC module or technique I could use instead of calling IPC::Open3?

Thanks!

I've tried basic permutations like:

  1. my @cmd = ('ls', "--color='auto'");
  2. my @cmd = ('ls', '--color=\'auto\'');
  3. my @cmd = ('ls', "--color=\"auto\"");

I've tried using IPC::Run::run() like so: $success = IPC::Run::run(@cmd, \$in, \$out, \$err, IPC::Run::timeout(5));

However, I can't figure out a way to prevent IPC::Open3 from double quoting.

Dave Koston
  • 254
  • 2
  • 6
  • 1
    I doubt it's adding anything; you're just used to a shell *removing* the quotes, which doesn't happen because a shell isn't involved in running the command here. – Shawn Jul 18 '23 at 23:09
  • 1
    (When run from sh, bash, zsh, etc, `ls --color="auto"` will pass the argument `--color=auto` to `ls`.) – Shawn Jul 18 '23 at 23:12
  • It was actually Perl adding quotes to the string. I printed the @cmd array with Data::Dumper::Dumper() and it was already quoted and escaped. The only thing that ended up working was stripping all single or double quotes and passing “—param=two words” as a single string in the array of params. – Dave Koston Jul 20 '23 at 03:37

2 Answers2

2

In sh-ish shells, the following all result in the token --color=auto:

  • --color=auto
  • '--color=auto'
  • "--color=auto"
  • --color="auto"
  • \-\-c'o'l"o"r\=auto

So when you use the shell command ls --color="auto", it passes two arguments to the program:

  1. ls
  2. --color=auto

So you want

my @cmd = ( 'ls', '--color=auto' );

Warning

The following can deadlock:

my @out = <$child_stdout>;
my @err = <$child_stderr>;

You'd have to use a complicated select loop or similar to do this safely.

Or you could use a different module.

use IPC::Run qw( run );

my @cmd = ( 'ls', '--color=auto' );

run \@cmd,
   '<',  '/dev/null',
   '>',  \my $out,
   '2>', \my $err;

die( "Child killed by signal ".( $? & 0x7F )."\n" ) if $? & 0x7F;
die( "Child exited with error ".( $? >> 8 )."\n"  ) if $? >> 8;
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • I didn’t think I could pass ‘—color=two words’ but since I’m passing as an array of rags to the command, it did work. – Dave Koston Jul 20 '23 at 03:35
  • The shell command fragments `--color="two words"` and `"--color=two words"` create the single token `--color=two words`, just like the Perl code `"--color=two words"` creates the string `--color=two words`. The quotes are not part of the string being built and passed to the program (in the shell or Perl). – ikegami Jul 20 '23 at 16:06
0

It turns out this had nothing to do with IPC::Open3 but it’s a behavior quirk of Perl where it escapes quoted parts of strings so it can print them later. I ended up using Data::Dumper::Dumper() right after setting @cmd and it was already quoted. So, I had to strip all quotes and do “—param=two words” when passing the param. I didn’t think that would work but since it’s ending up as it’s own param passed to execve/posix_spawn/whatever, there’s no need to quote the param value with spaces in it.

Sorry for getting people off track with the “—-color=‘auto’” example.

Another possibility was to try String::Escape::unprintable() but it was easier for me to just strip the quotes.

Dave Koston
  • 254
  • 2
  • 6