http://qs321.pair.com?node_id=200082

grinder has asked for the wisdom of the Perl Monks concerning the following question:

Suppose I want to do something like:

my @output = `/bin/foo -p stuff`;

If I want, I could build a list of arguments up on the fly, and pass the list to the command to be run, and everything would be funky.

my @args = map{ ($_, foo($arg{$_})) } keys %args; my @output = `/bin/foo @args`;

I have a couple of problems with this, in that the @args variable doesn't really serve any purpose, apart from allowing me to avoid the rather cumbersome:

my @output = `/bin/foo @{[map{ ($_, foo($arg{$_})) } keys %args]}`;

But in any event it just so happens that foo() can return strings containing $ characters, which are then munged by the shell, and the called program gets confused. I can't use the single quoted variant of qx//, because then @args won't be interpolated, which is even worse.

In Programming Perl 3rd Edition, it explains that "backticks don't have a list-argument form that is proof against the shell," but it goes on to say "a multi-argument form of the underlying readpipe operator is in development; but as of this writing, it isn't quite ready for prime time".

In terms of system, you can choose, or not, to pass through the underlying shell by doing:

system( '/bin/foo -p stuff' ); # passes by the shell system( '/bin/foo', '-p', 'stuff' ); # bypasses the shell

Backticked commands are always fed to the shell. This poses a problem if the command line in question contains a dollar sign, because the shell will try and interpolate an evironment variable in its place and all hell breaks loose.

That is, there is no syntax to do:

my @out = `/bin/foo`, qw/-p stuff/;

You have to go the whole hog, and fork and read back from your child. Which gives something like:

my $pid; if( !defined($pid = open( KIDP, '-|' ))) { print child fork error: $!\n"; } elsif( $pid ) { while( <KID> ) { chomp; # digest $_ } close KID; } else { $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; my @args = map{ (foo, foo($_)) } keys %data; exec '/usr/local/bin/check-netscreen', @args; die "ping child died \$!=$! \$@=$@\nargs = @args"; }

So I wondered, given PP's hint, about using readpipe but the results were less than glorious.

Running the following code...

#! /usr/bin/perl -w use strict; my @out = readpipe( '/bin/foo', qw/one two three/ );

... produces the expected output except that the first line returned is the name of the command (a problem we can get around easily enough, if it's strictly reproducible). Unfortunately, the last element of @args is not processed correctly. It does not appear to be passed to the called program, and the script emits the following to STDERR:

Can't exec "three": No such file or directory

On the other hand, the shell is bypassed, which is what I wanted. This is perl 5.6.1. Hence my main questions, is readpipe (finally) still not ready for prime time? and/or, is there a better way to that I have overlooked to allow you to perform backticked commands that bypass the shell? That is, replacing 17 lines of code by 1. This looks like a case where Perl doesn't make the difficult things very easy, but like I said, I'm probably missing something obvious.


print@_{sort keys %_},$/if%_=split//,'= & *a?b:e\f/h^h!j+n,o@o;r$s-t%t#u'