in reply to Automating sudo actions

Does anyone know any tricks for manipulating a bash session via Perl without Expect?

Here is a commandline and a Tk front end to a bash shell interpreter. A select loop would replace the Tk event loop's fileevent in the cmd version.

#!/usr/bin/perl use warnings; use strict; use IPC::Open3; use IO::Select; my $pid = open3(\*WRITE, \*READ,\*ERROR,"/bin/bash"); my $sel = new IO::Select(); $sel->add(\*READ); $sel->add(\*ERROR); my($error,$answer)=('',''); while(1){ print "Enter command\n"; chomp(my $query = <STDIN>); #send query to bash print WRITE "$query\n"; foreach my $h ($sel->can_read) { my $buf = ''; if ($h eq \*ERROR) { sysread(ERROR,$buf,4096); if($buf){print "ERROR-> $buf\n"} } else { sysread(READ,$buf,4096); if($buf){print "$query = $buf\n"} } } } waitpid($pid, 1); # It is important to waitpid on your child process, # otherwise zombies could be created.

And the Tk version:

#!/usr/bin/perl use warnings; use strict; use Tk; use IPC::Open3; require Tk::ROText; $|=1; my $mw = new MainWindow; my $entry=$mw->Entry(-width => 80)->pack; $mw->Button(-text => 'Execute', -command => \&send_to_shell)->pack; my $textwin =$mw->Scrolled('ROText', -width => 80, -bg =>'white', -height => 24, )->pack; $textwin->tagConfigure( 'err', -foreground => 'red' ); my $pid = open3( \*IN, \*OUT, \*ERR, '/bin/bash' ) or warn "$!\n"; $mw->fileevent( \*OUT, readable => \&read_stdout ); $mw->fileevent( \*ERR, readable => \&read_stderr ); $entry->bind('<Return>',[\&send_to_shell]); $entry->focus; MainLoop; sub read_stdout { if( sysread( OUT, my $buffer, 1024 ) > 0 ){ $textwin->insert( 'end', $buffer ); $textwin->see('end'); } } sub read_stderr { if( sysread(ERR, my $buffer, 1024 ) > 0 ){ $textwin->insert( 'end', $buffer, 'err' ); $textwin->see('end'); } } sub send_to_shell { my $cmd= $entry->get(); print IN "$cmd\n"; }

I'm not really a human, but I play one on earth.
Old Perl Programmer Haiku ................... flash japh

Replies are listed 'Best First'.
Re^2: Automating sudo actions
by rastoboy (Monk) on Jan 25, 2011 at 23:42 UTC
    That is totally awesome zentara, thanks so much!

    I never would have guessed that IO::Select was necessary--can you explain why it is? I've had lots of weirdness using IPC::Open3 in the past and I suspect there's something unixy I don't understand properly.

    I always thought that IPC::Open3 was designed specifically so that you could just talk and listen to a process you spawned, but it doesn't seem to work that way (for long) in practice.

      The reason you need select or else IO::Select is very simple. When you are both talking to and listening from a process it is very important that you are not talking when it is trying to talk, or listening when it is trying to listen. If that happens you'll get a deadlock which lasts forever.

      What select (either version) does is tell you whether it is ready to talk or receive more input, so that you can correctly send or receive data. That way you know which you can safely do and avoid deadlocks.

      Please note that doing this correctly is complicated. If at all possible you want to arrange to need to talk to an external process, or listen from it, but not both.

      In addition to what tilly said about select, in this case there is an additional, and often useful technique to actually SEPARATE the stdout and stderr stream. In IPC::Open3, if you set the \*ERR handle to 0, it will go to stdout, like in IPC::Open2. But what if you wanted your stderr to go somewhere different, or be highlighted in different colors, or go to a log file?. Then the general purpose techique shown above can be used. It is almost the way you would do it in c. Most people forget about select, because many modules handle those details transparently for them.

      But it is a cute code fragment, isn't it? You know, us old geeks, drool over elegant time-tested code fragments. :-)


      I'm not really a human, but I play one on earth.
      Old Perl Programmer Haiku ................... flash japh