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. | [reply] [d/l] |
|
|
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.
| [reply] [d/l] [select] |
|
|
those are better solutions!
| [reply] |
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;
}
}
| [reply] [d/l] |
|
|
| [reply] |
|
|
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.
| [reply] [d/l] [select] |
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! |
|---|
| [reply] [d/l] |
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) | [reply] [d/l] |
|
|
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.
| [reply] |
|
|
No offense taken, but I don't understand how IPC::OpenX is going to help you, if you can't ssh to the machine.
| [reply] |
|
|
Re^2: Short example using IPC::Open2 or IPC::Open3
by runrig (Abbot) on Oct 23, 2007 at 21:30 UTC
|
| [reply] |
|
|
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.
| [reply] |
|
|
| [reply] |
|
|
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
| [reply] [d/l] [select] |
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;
| [reply] [d/l] |