tags:

views:

428

answers:

3

I have a Perl script which does this: It generates a ssh authentication key on my system and then copies this key to a remote Linux system for passwordless ssh connects. The code is as below:

# Generate an rsa key and store it in the given file
system("ssh-keygen -t rsa -N '' -f /root/.ssh/id_rsa 1>/dev/null"); 

# Copy the generated key to a remote system whose username 
# is stored in variable $uname and IP address is stored in variable $ip 
system("ssh-copy-id -i /root/.ssh/id_rsa.pub $uname\@$ip 2>&1 1>/dev/null");

The problem I have is this: The ssh-copy-id command takes quite some time to copy the key to the remote system. So, when this Perl script is run, it will appear like the script has hung.

Therefore, I want to do display a "progress message": When the copy is in progress I want to display "SSH authentication key copy is progress" and if the copy has failed, display "Failed to copy" and if it has succeeded, display "Copy succeeded".

How do I go about doing this?

One more(based on Chas's answer):
I tried the code as Chas suggested
die "could not fork: $!" unless defined(my $pid = fork);

#child sleeps then exits with a random exit code
unless ($pid) {
print "Connecting to server "; exec "ssh-copy-id -i /root/.ssh/id_rsa.pub $uname\@$ip 2>&1 1>/dev/null";
exit int rand 255;
}

#parent waits for child to finish
$| = 1;
print "waiting: ";
my @throbber = qw/ . o O o . /;
until ($pid = waitpid(-1, WNOHANG)) {
#get the next frame
my $frame = shift @throbber;

#display it<br />
print $frame;


#put it at the end of the list of frames
push @throbber, $frame;

#wait a quarter second<br />
select undef, undef, undef, .25;<br />

#backspace over the frame<br />
print "\b";<br />

}

The problem is this:
Now ssh-copy-id asks for a Password input while connecting to the remote server. So, the "throbber" output(i.e the circle of varying size that get's displayed) comes after the Password input which looks weird. This is how it looks like:
CURRENT OUTPUT
Connecting to remote server o0O0o #This is the throbber. The output doesn't exactly look like this but I can't print dynamically changing output, can I
Password:o0O0oXXXXXo0O0o #You get it right, the throbber unnecessarily comes at the Password prompt too

THE OUTPUT THAT I WANT:
Connecting to remote server o0O0o #The throbber should be displayed HERE ONLY, NOWHERE ELSE
Password:XXXXX

Any ideas, anyone?

+2  A: 

system() returns the exit status of the program as returned by the wait call. Nothing is returned if the system call was successful.

Thus u can see code like this:

system( 'ls' ) and die "Unable to call ls: $?";

which is very unintuitive ;-)

I normally do the following:

my $status = system( 'ls' );
die "Unable to call ls: $?" if $status;

However if you look at the perldoc you see a nicer alternative:

system( 'ls' ) == 0
    or die "Unable to call ls: $?"

I would go with this method. But it would be amiss of me not to mention method suggested in the Perl Best Practices book:

use Perl6::Builtins qw( system );

system 'ls' or die "Unable to run ls: $?";

However note the PBP recommendation list points you away from this towards using autodie:

use autodie qw( system );

eval { system 'ls' };
die "Unable to run ls: $@" if $@;

So this is probably the canonical way going forward.

/I3az/

draegtun
+1  A: 

I don't think it's possible in an easy way (without resorting to forking, timers and whatnot) to add a progress bar to a single external command run via system() that doesn't generate output.

On the other hand, I think the reason your ssh-copy-id takes a long time to complete is because of an inproper DNS setup (check the sshd logs on the server side for a clue). The ssh server probably tries to resolve the reverse mapping for the client IP and times out. Fixing that will probably speed things up quite a lot.

When it comes to your messages. Can't you just use a print before running the system() command and using the return value from system to print the completion message (as already suggested by some of the other answers)?

Robin Smidsrød
Yes, that would make things less complex :)
Ninja
+3  A: 

It is fairly simple to fork off another process to do work for you while the main process lets the user know things haven't stopped happening:

#!/usr/bin/perl

use strict;
use warnings;

use POSIX ":sys_wait_h";

die "could not fork: $!" unless defined(my $pid = fork);

#child sleeps then exits with a random exit code
unless ($pid) {
    #your code replaces this code
    #in this case, it should probably just be
    #exec "ssh-copy-id -i /root/.ssh/id_rsa.pub $uname\@$ip 2>&1 1>/dev/null";
    #as that will replace the child process with ssh-copy-id
    sleep 5;
    exit int rand 255;
}

#parent waits for child to finish
$| = 1;
print "waiting: ";
my @throbber = qw/ . o O o . /;
until ($pid = waitpid(-1, WNOHANG)) {
    #get the next frame
    my $frame = shift @throbber;

    #display it
    print $frame;

    #put it at the end of the list of frames
    push @throbber, $frame;

    #wait a quarter second
    select undef, undef, undef, .25;

    #backspace over the frame
    print "\b";
}
#exit code is in bits 8 - 15 of $?, so shift them down to 0 - 7
my $exit_code = $? >> 8;
print "got exit code of $exit_code\n";
Chas. Owens
Cool answer! Thanks!
Ninja