Limbic~Region has asked for the wisdom of the Perl Monks concerning the following question:

All,
I have been using perl for over 5 years now and have never needed IPC::Open2 or IPC::Open3. I have a task that should use Net::SFTP but due to restrictions beyond my control, this is not an option. I have ssh keys set up so authentication is not an issue.

I was hoping someone could provide an example of how the folowing series of commands could be executed using IPC::Open2 or IPC::Open3.

Even if this is the wrong way to do it, I would still appreciate an example so that I will know in the future.

Cheers - L~R

  • Comment on Short example using IPC::Open2 or IPC::Open3

Replies are listed 'Best First'.
Re: Short example using IPC::Open2 or IPC::Open3
by gamache (Friar) on Oct 23, 2007 at 21:06 UTC
    Try this. Nonblocking read is necessary.
    #!/usr/bin/perl use strict; use warnings; use IPC::Open2; use Fcntl qw(F_SETFL F_GETFL O_NONBLOCK); my ($rh, $wh); my $pid = open2 ($rh, $wh, "sftp somehost") or die "can't sftp: $!"; my $flags = fcntl ($rh, F_GETFL, 0) or die "can't get flags: $!"; fcntl ($rh, F_SETFL, $flags|O_NONBLOCK) or die "can't set O_NONBLOCK: +$!"; print $wh "ls foo\n"; my @files; my $ln; while (! ($ln = <$rh>)) {} ## wait for valid output do { $ln=~/.+sftp>\s*//; ## eat sftp> prompts chomp $ln; push @files, $ln; } while ($ln = <$rh>); for (@files) { my $qf = quotemeta; print $wh "get foo/$qf\n"; print $wh "mv foo/$qf bar/$qf\n"; } print $wh "bye\n"; waitpid $pid, 0;
    I couldn't test this 100% as-is but a similar version worked.

    Edited (twice) to ignore sftp> prompts.

      Problem #1: Since the "from child" filehandle is non-blocking, the parent could read from the pipe faster than it fills up, causing @files to contain only a partial list. Since the data is coming from the internet, that's actually quite possible. Why did you make it unblockable anyway? Just exit the loop when sftp> is encountered.

      Problem #2: You don't empty the pipe attached to sftp's STDOUT once you've done reading file names, so it could fill up and cause sftp to block.

        those are better solutions!
Re: Short example using IPC::Open2 or IPC::Open3
by ikegami (Patriarch) on Oct 23, 2007 at 20:28 UTC

    Is HTTP an option? Commands, responses and error messages are already segregated — that's the hard part — so it easily achieve your goal using a small client driving a small CGI script.

    Client:

    my @files = do { my $list_response = $ua->get("$uri?action=list_files"); if (!$list_response->is_success()) { die(...); } split /\n/, $response->content() }; for my $file (@files) { my $esc_file = uri_escape($file); my $get_response = $ua->get( "$uri?action=get&file=$esc_file", ":content_file" => $file, ); if (!$get_response->is_success()) { unlink($file); warn(...); next; } my $move_response = $ua->post( "$uri?action=move&file=$esc_file", ); if (!$move_response->is_success()) { warn(...); next; } }
      ikegami,
      Is HTTP an option?

      Nope. The only access to the remote server is via sftp. Even then, many commands have been disabled. It will not be the end of the world if I can't do this the way I want but I would still like to see the example of IPC::Open2. This is more for my own education than it is to solve a problem.

      Cheers - L~R

        It's hard communicating with an interactive application. It might even be impossible without using a pseudo tty if it buffers its output when STDOUT isn't a terminal (like Perl does).

        Since sftp can work non-interactively, you'd be better off using calling sftp multiple times, once for each action you wish to perform. open $fr_sftp, '-|', 'sftp', ... would work nicely for that.

        But since you're interested in open2 for educational purposes, I'd start with the following.

        { package SFTP; use IO::Handle qw( ); sub new { my $class = shift(@_); my ($to_sftp, $fr_sftp); # open2 dies on error. my $pid = open2($to_sftp, $fr_sftp, 'sftp', @_); $to_sftp->autoflush(1); return bless({ pid => $pid, to_sftp => $to_sftp, fr_sftp => $fr_sftp, }, $class); } sub DESTROY { my ($self) = @_; ...send exit command to child... ...kill child if necessary... waitpid($self->{pid}, 0); } sub do { my ($self) = @_; my $fr_sftp = $self->{fr_sftp}; my $to_sftp = $self->{to_sftp}; print $to_sftp "command"; my $response; for (;;) { my $line = <$fr_sftp>; last if $line =~ $prompt; $response .= $line; } return $response; } } my $sftp = SFTP->new(...sftp args...); my $response = $sftp->do("...\n");

        open3 handles STDERR, but that makes the parent code more complicated since it needs to use select to avoid the deadlock that occurs when the child blocks writing to its STDERR and the parent blocks reading from the child's STDOUT and vice-versa.

Re: Short example using IPC::Open2 or IPC::Open3
by jasonk (Parson) on Oct 24, 2007 at 01:22 UTC

    If you really want to interact with the sftp cli, I think Expect is nearly always a better choice than IPC::Open2 or IPC::Open3. That being said, however, there is an sftp option that can make this a lot easier with just backticks...

    open( OUT, ">/tmp/sftp-batch" ); print OUT <<"END"; cd foo ls END close( OUT ); chomp( my @files = `sftp -b /tmp/sftp-batch hostname` ); open(OUT, ">/tmp/sftp-batch-2" ); print OUT <<"END"; cd foo lcd bar END for my $file ( @files ) { print OUT "get $file\n"; } system( "sftp -b /tmp/sftp-batch-2 hostname" );

    We're not surrounded, we're in a target-rich environment!
Re: Short example using IPC::Open2 or IPC::Open3
by gamache (Friar) on Oct 23, 2007 at 20:19 UTC
    If it were up to me, I'd skip IPC::Open\d and just do something like:
    chomp (my @files = `ssh somehost ls foo`); for (@files) { my $qf = quotemeta; system "scp somehost:foo/$qf ."; system "ssh somehost mv foo/$qf bar/$qf"; }
    (Update: see below for a solution pertinent to L~R's actual needs)
      gamache,
      You assume that one can SSH to the host. In this case, shell access has been removed. Please don't take this the wrong way but I wouldn't have asked for an example of IPC::Open2 (even if it was the wrong way to do it) if that wasn't what I was interested in.

      Cheers - L~R

        No offense taken, but I don't understand how IPC::OpenX is going to help you, if you can't ssh to the machine.
Re^2: Short example using IPC::Open2 or IPC::Open3
by runrig (Abbot) on Oct 23, 2007 at 21:30 UTC
      runrig,
      If it isn't already installed on the box and it requires compiling then no. The situation is that there is a task currently being performed manually by a system administrator on a production machine. There is an officially blessed development activity to automate most of these manual tasks. It is being done in Java by another group and is taking what I feel is an excessive amount of time.

      I am trying to help out by giving some relief in the short term. Requesting a new package (and dependencies) be installed on a production machine for "throw away" code is a battle I am not interested in fighting. I am sure I could fight and win making the argument that the tools will be available for the next project but it is not worth it to me personally.

      As a person always trying to learn new things, I wanted to try and solve this problem using something I didn't already know how to do. There are a lot of solutions in this thread that are not new to me. That is fine. The monks are great that way. It just isn't what I am interested in.

      Cheers - L~R

Re: Short example using IPC::Open2 or IPC::Open3
by Ieronim (Friar) on Oct 23, 2007 at 21:50 UTC
    Although you want to study IPC::Open2, IMO it is good to know that your problem is solved by a two-line batch file and scp:
    #!/bin/sh scp -B -i keyfile -r user@host:./foo localdir scp -B -i keyfile -r localdir user@host:./bar

         s;;Just-me-not-h-Ni-m-P-Ni-lm-I-ar-O-Ni;;tr?IerONim-?HAcker ?d;print
Re: Short example using IPC::Open2 or IPC::Open3
by zentara (Cardinal) on Oct 24, 2007 at 10:51 UTC
    Here's another simplistic example I had laying around (I would first try to use Net::SSH2)
    #!/usr/bin/perl use FileHandle; use IPC::Open2; my(@machines,$host,$user,$pass); # read your record open(INFILE,"<IPC2-SSH-machines.txt") || die "Error opening machines.t +xt.$!,stopped"; chomp ( @machines = <INFILE> ); close(INFILE); foreach my $rec (@machines) { ($host,$user,$pass) = split(/,/, $rec); print "$host $user $pass\n\n"; open2(*RD_FH, *WR_FH, "ssh $user\@$host uptime") || die "cant fork a c +hild: $!"; while (<RD_FH>){print}; print WR_FH "$pass"; while (<RD_FH>){ if(m/password/){ #if(1){ #print WR_FH "$pass\n"; print "\t\t\tHOST $host responded with\n"; print "__________________________________________________________\n"; }else{ print "error msg from ssh: $_\n"; } } } exit 0;

    I'm not really a human, but I play one on earth. Cogito ergo sum a bum