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

Hi Monks,
I am using IPC::Open2 for controlling INPUT AND OUTPUT of a programme (bash shell). But, I am facing a little bit trouble. When I am giving a command (which I am taking from STDIN, processing it and giving to shell). The command, which I am giving to SHELL, if it is correct I am getting it's output, but if I am giving a wrong command I am getting error. But, from then onwards I am not getting output of even correct command. what could be wrong ?? Can anybody help me ....

Thanks ....

#! /usr/bin/perl -w use strict; use IPC::Open2; use FileHandle; do { my $pid = open2( \*Reader, \*Writer, "bash" ); Writer->autoflush(); # default here, actually Reader->autoflush(); STDIN->autoflush(); my $cmd = <STDIN>; print Writer $cmd; my $got = <Reader>; print $got; } while(1);

Replies are listed 'Best First'.
Re: Usage of IPC::Open2 ...
by Dominus (Parson) on Nov 25, 2000 at 20:19 UTC
    Says galande:
    > if I am giving a wrong command I am getting error.
    > But, from then onwards I am not getting output of
    > even correct command. what could be wrong ??
    You have:
    my $cmd = <STDIN>; print Writer $cmd; my $got = <Reader>; print $got;
    Suppose $cmd is a bad command. You send it to the shell. Then you try to read from Reader. This waits for the shell to print a line on its stdout. But when $cmd is bad, the shell prints nothing on stdout. Instead, it prints an error message on its stderr. The line my $got = <Reader>; waits for stdout from the shell, but there is none. It waits forever.

    This is exactly the problem that is explained in the manual:

    This whole affair is quite dangerous, as you may block forever.
    That is what has happened to you.

    One quick way to fix this: Instead of bash, try bash 2>&1. Then $got will get the error message from bash.

    To fix this properly, you would need to use the select function. select will tell you whether Reader has anything to read. Then read from Reader only when there is data available. This is difficult to do right, so I recommend that you try something else first.

    Probably the best thing is as repson suggested and forget about IPC::Open2. Use $got = qx{$cmd} instead.

      Note also that IPC::Open3 will do the same thing as open2, but catches stderr also, so that's another option.

      Update: As Dominus kindly pointed out, this does not solve the immediate problem, but if you decide that differentiating between STDERR/STDOUT is something you will end up needing to do with this specific issue or in a future one, IPC::Open3 is something you may wish to look into. Obviously you can't use it as a drop-in replacement for open2 in your code and have things magically work. It's an alternative at more of a design level.

        Says fastolfe:
        > Note also that IPC::Open3 will do the same thing as open2,
        >but catches stderr also, so that's another option.
        It may be another option for someone, but not for galande. Did you read the original question? The problem was that galande is reading from the child's stdout when there is nothing there, and that causes the parent to block. Switching from Open2 to Open3 is not going to make a difference.

Re: Usage of IPC::Open2 ...
by repson (Chaplain) on Nov 25, 2000 at 18:52 UTC
    Okay I can see several issues with your code.

    First off what you are actually doing here can be done with a simple qx// or backticks. print qw/$cmd/ could replace your loop. This code would be much more sensible than what you are doing if what you intend is simply to get the output of running $cmd.

    Note: You should be running with taint checks on when running user input in the shell if there is more to this program then simply providing a direct wrapper around bash from the shell.

    However if you require a persistent shell (though I don't see why you would in normal situations) then you are on the right track. The first problem is that for a persistent shell you would want to move open2 and the autoflush statements outside the loop. You should also print more than one line of output from the shell in the loop.
    Here is my code to accomplish this task:

    #same initialization my $pid = open2( \*Reader, \*Writer, "/bin/bash" ) or warn $!; Writer->autoflush(); Reader->autoflush(); STDIN->autoflush(); $SIG{ALRM} = sub {die}; # required for alarm call below do { print ": "; # simple prompt my $cmd = <STDIN>; print Writer $cmd; my $got; while (1) { eval { alarm 3; $_=<Reader> }; # get line in 3 seconds or stop + reading alarm 0; last if ($@ or !(defined($_))); # exit loop if there was a tim +eout or no data $got .= $_; } print $got; } while(1);
    If you need help understanding this code or have further problems, feel free to ask.
Re: Usage of IPC::Open2 ...
by Fastolfe (Vicar) on Nov 26, 2000 at 09:10 UTC
    If all you want is code that interacts with a command like this (such as 'bash'), I would recommend foregoing all of this and using the Expect module.