Jos I. Boumans wrote:

>On Feb 18, 2005, at 2:10 PM, Steve Hay wrote:
>  
>
>  
>
>>The attached program contains what I believe is the relevant code, 
>>lifted from CPANPLUS::Tools::Cmd, to try open IPC::Open3 vs IPC::Run.
>>    
>>
>Best to lift from IPC::Cmd, as that's the library we're actually using 
>-- cpanplus::tools::cmd is rather obsolete (luckily the ipc::open3 
>implementation hasn't changed.. the ipc::run one has though)
>  
>
OK, I've updated the attached program to use code from IPC::Cmd.

>  
>
>
>  
>
>>Presumably IO::Select->can_read() doesn't work on Win32 because it 
>>uses a 4-arg select(), which is only implemented for sockets on Win32. 
>> Is the above change safe, or did we need to call can_read() for some 
>>reason?  Would the proposed "selectable pipes" change have any impact 
>>on this?
>>    
>>
>I have no idea myself, i'm not much of an IPC guru, i just RTFM'd on 
>this. But if others can assure me it's safe & portable, it's an easy 
>patch to do.
>  
>
Ton says it isn't (always) safe, so I think that's not the way to go :(

>  
>
>>What do I need to do to test out whether IPC::Open3 is working with 
>>pipes or not?  (Open3.t doesn't seem to include any such tests.)
>>    
>>
>That might be a very useful addition to the open3.t tests...
>Perhaps something like this would work as a test case:
>       "$^X -e'print 1' | $^X -e'print 1 + <>'"
>
I've put that $cmd into the attached test.pl, but something's not 
right.  The IPC::Run version with this new $cmd doesn't work.  It gives 
me the error:

'ARRAY' not allowed as a source for input redirection at test.pl line 68

I think the scan for $special_chars has got confused over the "<>" in 
the Perl one-liner -- it thinks they are shell redirection characters.  
Presumably this would affect other OS's too, and is really a bug in 
IPC::Cmd?

Using IPC::Open3 I get the same as "perl -v" gave me before:

err=[0]
buffer=[]
buferr=[]
bufout=[]

and again removing the can_read() calls "fixes" it:

err=[0]
buffer=[2]
buferr=[]
bufout=[2]

That's great news, as it gives us hope that IPC::Open3 + pipes is OK.  
So the only issue here is that can_read() doesn't work because pipes 
aren't selectable on Win32.  I'm therefore interested what work would be 
involved (either using C code in the core, or equivalent Perl code in 
IPC::Cmd) to fix it.

- Steve


------------------------------------------------
Radan Computational Ltd.

The information contained in this message and any files transmitted with it are 
confidential and intended for the addressee(s) only.  If you have received this 
message in error or there are any problems, please notify the sender 
immediately.  The unauthorized use, disclosure, copying or alteration of this 
message is strictly forbidden.  Note that any views or opinions presented in 
this email are solely those of the author and do not necessarily represent 
those of Radan Computational Ltd.  The recipient(s) of this message should 
check it and any attached files for viruses: Radan Computational will accept no 
liability for any damage caused by any virus transmitted by this email.
use strict;
use warnings;

use IPC::Open3;
use IPC::Run;
use IO::Select;
use Symbol;

MAIN: {
    my (@buffer,@buferr,@bufout);
    my $verbose = 0;

    ### STDOUT message handler
    my $_out_handler = sub {
        my $buf = shift;
        return unless defined $buf;

        print STDOUT $buf if $verbose;
        push @buffer, $buf;
        push @bufout, $buf;
    };

    ### STDERR message handler
    my $_err_handler = sub {
        my $buf = shift;
        return unless defined $buf;

        print STDERR $buf if $verbose;
        push @buffer, $buf;
        push @buferr, $buf;
    };

#    my $cmd = "$^X -v";
    my $cmd = qq[$^X -e "print 1" | $^X -e "print 1 + <>"];
    my @cmd = ref ($cmd) ? grep(length, @{$cmd}) : $cmd;

    printf "Running [%s]...\n", join(' ', @cmd) if $verbose;

    my $err;
    if (@ARGV and $ARGV[0] =~ /run/) {
        STDOUT->autoflush(1); STDERR->autoflush(1);

        my @command; my $special_chars;
        if (ref $cmd) {
            my $aref = [];
            for my $item (@cmd) {
                if ($item =~ /[<>|&]/) {
                    push @command, $aref, $item;
                    $aref = [];
                    $special_chars++;
                }
                else {
                    push @$aref, $item;
                }
            }            
            push @command, $aref;
        }
        else {
            @command = map { if (/[<>|&]/) {
                                $special_chars++; $_;
                             }
                             else {                            
                                [ split / +/ ]
                             }
                        } split(/\s*([<>|&])\s*/, $cmd);
        }

        if ($special_chars) {              
            IPC::Run::run(@command, \*STDIN, '>', $_out_handler, 
                                             '>', $_err_handler) or $err++;
        }
        else {
            IPC::Run::run(@command, \*STDIN, $_out_handler, 
                                         $_err_handler) or $err++;
        }
    }
    elsif (@ARGV and $ARGV[0] =~ /open/) {
        my $rv;
        ($rv,$err) = _open3_run([EMAIL PROTECTED], $_out_handler, $_err_handler, $verbose);
    }
    else {
        die "Usage: $0 {run|open}\n";
    }

    $err ||= $?;

    print "err=[$err]\n";
    print "[EMAIL PROTECTED]";
    print "[EMAIL PROTECTED]";
    print "[EMAIL PROTECTED]";
}

sub _open3_run {
    my ($cmdref, $_out_handler, $_err_handler, $verbose) = @_;

    my $cmd = join " ", @$cmdref;

    my ($infh, $outfh, $errfh); # open3 handles

    my $pid = eval {
        IPC::Open3::open3(
            $infh   = Symbol::gensym(),
            $outfh  = Symbol::gensym(),
            $errfh  = Symbol::gensym(),
            $cmd,
        )
    };

    return (undef, $@) if $@;

    my $sel = IO::Select->new; # create a select object
    $sel->add($outfh, $errfh); # and add the fhs

    STDOUT->autoflush(1); STDERR->autoflush(1);
    $outfh->autoflush(1) if UNIVERSAL::can($outfh, 'autoflush');
    $errfh->autoflush(1) if UNIVERSAL::can($errfh, 'autoflush');

    while (my @ready = $sel->can_read) {
        foreach my $fh (@ready) { # loop through buffered handles
            # read up to 4096 bytes from this fh.
            my $len = sysread $fh, my($buf), 4096;

            if (not defined $len){
                # There was an error reading
                warn "Error from child: $!";
                return(undef, $!);
            }
            elsif ($len == 0){
                $sel->remove($fh); # finished reading
                next;
            }
            elsif ($fh == $outfh) {
                $_out_handler->($buf);
            } elsif ($fh == $errfh) {
                $_err_handler->($buf);
            } else {
                warn "IO::Select error";
                return(undef, $!);
            }
        }
    }

    waitpid $pid, 0; # wait for it to die
    return 1;
}

Reply via email to