views:

137

answers:

5

I'm writing a perl script that mimics gcc. This my script needs to process some stdout from gcc. The part for processing is done, but I can't get the simple part working: how can I forward all the command line parameters as is to the next process (gcc in my case). Command lines sent to gcc tend to be very long and can potentially contain lots of escape sequences and I don't want now to play that game with escaping and I know that it's tricky to get it right on windows in complicated cases.

Basically, gcc.pl some crazies\ t\\ "command line\"" and that gcc.pl has to forward that same command line to real gcc.exe (I use windows).

I do it like that: open("gcc.exe $cmdline 2>&1 |") so that stderr from gcc is fed to stdout and I my perl script processes stdout. The problem is that I can't find anywhere how to construct that $cmdline.

+1  A: 

Read up on the exec function and the system function in Perl.

If you provide either of these with an array of arguments (rather than a single string), it invokes the Unix execve() function or a close relative directly, without letting the shell interpret anything, exactly as you need it to do.

Jonathan Leffler
That's close, but I originally opted to use open because I actually need some assistance from shell: I forward stderr to stdout and then I feed stdout to my script. system (if it does the same as system in C) doesn't allow me to process stdout (unless I do some black magic trickery with std file descriptors) and exec won't allow me to do it either (obviously). Also, I need to process line by line (my program that I compile with g++/gcc is very complex takes seconds to compile for every file and I need to process line by line instead of freezing for seconds and then dumping all stdout.
PPS
So, at this point, was my decision correct to use open or I need to look into exec/system calls?It seems that quoting rules aren't that complicated on windows and it seems that I should just write it myself by hand (php for exmaple has simple api for shell quoting that would work well for my case).
PPS
open accepts a list just as exec and system do, unless your perl is very very old.
hobbs
+1  A: 

I would use AnyEvent::Subprocess:

use AnyEvent::Subprocess;

my $process_line = sub { say "got line: $_[0]" };

my $gcc = AnyEvent::Subprocess->new(
    code      => ['gcc.exe', @ARGV],
    delegates => [ 'CompletionCondvar', 'StandardHandles', {
         MonitorHandle => {
              handle   => 'stdout',
              callback => $process_line,
         }}, {
         MonitorHandle => {
              handle   => 'stderr',
              callback => $process_line,
         }},
    ],
);

my $running = $gcc->run;
my $done = $running->recv;
$done->is_success or die "OH NOES";

say "it worked";

The MonitorHandle delegate works like redirection, except you have the option of using a separate filter for each of stdout and stderr. The "code" arg is an arrayref representing a command to run.

jrockway
That seems like what I need ;) thanks!I know many tools/sdks use that technique, but I just don't have any of them that do that kind of forwarding to see how they implemented it in perl. I do that forwarding of stderr to stdout so that I wouldn't need to process them separately (which may deadlock if I don't do it properly). Seems like AnyEvent::Subprocess is the right solution for me.
PPS
Actually, I just tried installing AE::Subprocess on Windows, and things don't work right. Sigh. Use cygwin :)
jrockway
Won't work, I run all that junk from withing VisualStudio build process, so cygwin isn't an option. I tried to look inside AE::Subprocess for that win32 specific code they might use for quoting, but didn't see anything.
PPS
A: 

Thanks for answers, I came to conclusion that I made a big mistake that I touched perl again: hours of time wasted to find out that it can't be done properly. Perl uses different way to split command line parameters than all other apps that use MS stdlib (which is standard on win32).

Because of that some commandline parameters that were meant to be interpreted as a signle commandline argument, by perl can be interpreted as more than one argument. That means that all what I'm trying to do is waste of time because of that buggy behavior in perl. It's impossible to get this task done correctly if I 1) can't access original command line as is and 2) perl doesn't split command line arguments correctly.

as a simple test:

script.pl """test |test"

on win32 will incorrectly interpret command line as:

ARGV=['"test', '|test']

Whereas, the correct "answer" on windows has to be

ARGV=['"test |test']

I used activestate perl, I tried also latest version of strawberry perl: both suck. It appears that perl that comes with msys works properly, most likely because it was built against mingw instead of cygwin runtime?..

The problem and reason with perl is that it has buggy cmd line parser and it won't work on windows NO MATTER WHAT cygwin supports or not. I have a simple case where an environment variable (which I cannot control) expands to

perl gcc.pl -c  "-IC:\ffmpeg\lib_avutil\" rest of args

Perl sees that I have two args only: -c and '-IC:\ffmpeg\lib_avutil" rest of args' whereas any conforming windows implementation receives second cmd line arg as: '-IC:\ffmpeg\lib_avutil\', that mean that perl is a huge pile of junk for my simple case, because it doesn't provide adequate means to access cmd line arguments. I'm better off using boost::regex and do all my parsing in c++ directly, at least I won't ever make dumb mistakes like ne and != for comparing strings etc. Windows's escaping rules for command line arguments are quite strange, but they are standard on windows and perl for some strange reason doesn't want to follow OS's rules.

PPS
I can't read this. can you try reformatting it?
Nathan Fellman
You're unjustly blaming `cmd.exe`'s shortcomings on perl. The command shell splits arguments, not perl. If you interpolate `@ARGV` directly into another command (that is, run *another* pass of the shell's argument parsing on it), you're going to get different results.
Greg Bacon
You are wrong about that. Command shell does not split args. On windows process gets command line as a string and libc does the argument split. The problem with all the perl builds on windows is probably because they use different algorithm (which is wrong in windows world) to split args (hint: they need to use msvcrt lib or write windows conforming variant).
PPS
+2  A: 

"Safe Pipe Opens" in the perlipc documentation describes how to get another command's output without having to worry about how the shell will parse it. The technique is typically used for securely handling untrusted inputs, but it also spares you the error-prone task of correctly escaping all the arguments.

Because it sidesteps the shell, you'll need to create the effect of 2>&1 yourself, but as you'll see below, it's straightforward to do.

#! /usr/bin/perl

use warnings;
use strict;

my $pid = open my $fromgcc, "-|";
die "$0: fork: $!" unless defined $pid;

if ($pid) {
  while (<$fromgcc>) {
    print "got: $_";
  }
}
else {
  # 2>&1
  open STDERR, ">&STDOUT" or warn "$0: dup STDERR: $!";

  no warnings "exec";  # so we can write our own message
  exec "gcc", @ARGV       or die  "$0: exec: $!";
}

Windows proper does not support open FH, "-|", but Cygwin does so happily:

$ ./gcc.pl foo.c
got: gcc: foo.c: No such file or directory
got: gcc: no input files
Greg Bacon
dup STDERR to STDOUT like this and you get the best of both worlds
mobrule