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

I'm using a subroutine to communicate with an SSH host. Each time i post a command i receive a errcode and the return value. If one of those isn't as expected, i exit my subroutine using return. However, before the return, i first have to exit the ssh session and then close the session.
if (!$ssh->start_session($ssh_host)) { print "ERROR connecting to $ssh_host\n"; return 1; } my ($ret,$err)=$ssh->execute('command1','expect1'); if ($err || $ret=~/Unknown command:/m) { $ssh->exit_session(); $ssh->close_session; return 2; } my ($ret,$err)=$ssh->execute('command2','expect2'); if ($err || $ret=~/Unknown command:/m) { $ssh->exit_session(); $ssh->close_session; return 3; }
Is there a better way to execute the exit, close and then the return instead of writing them after every statement to get out of the subroutine ? kr

Replies are listed 'Best First'.
Re: exiting a subroutine neatly
by salva (Canon) on May 05, 2015 at 09:39 UTC
    There are several ways to do it, but IMO, the easiest and most straight forward is as follows:
    sub foo { my $rc = 0; if (!$ssh->start_session($ssh_host)) { print "ERROR connecting to $ssh_host\n"; $rc = 1; goto end; } my ($ret,$err)=$ssh->execute('command1','expect1'); if ($err || $ret=~/Unknown command:/m) { $rc = 2; goto cleanup; } my ($ret,$err)=$ssh->execute('command2','expect2'); if ($err || $ret=~/Unknown command:/m) { $rc = 3; goto cleanup; } ... cleanup: $ssh->exit_session(); $ssh->close_session; end: return $rc; }

      salva: There are several ways to do it, but IMO, the easiest and most straight forward is as follows: goto end

      Um, two subroutines :)

      sub foo { ... my $rc = fooFoo($ssh); $ssh->exit_session(); $ssh->close_session; return $rc; } sub fooFoo { my ($ssh) = @_; if ( !$ssh->start_session($ssh_host) ) { print "ERROR connecting to $ssh_host\n"; return 1; } my ( $ret, $err ) = $ssh->execute( 'command1', 'expect1' ); if ( $err || $ret =~ /Unknown command:/m ) { return 2; } my ( $ret, $err ) = $ssh->execute( 'command2', 'expect2' ); if ( $err || $ret =~ /Unknown command:/m ) { return 3; } return 0; }
        speaking of which, c-style error checking/indicating return codes are a pita when you have Try::Tiny/die/eval/Throwable
        use Try::Catch qw/ try catch finally /; sub foo { ... try { fooFoo($ssh); } catch { warn "warn cought one: $_"; }; $ssh->exit_session; $ssh->close_session; } sub fooFoo { my ($ssh) = @_; if ( !$ssh->start_session($ssh_host) ) { MyErr->throw("ERROR connecting to $ssh_host"); } my ( $ret, $err ) = $ssh->execute( 'command1', 'expect1' ); if ( $err || $ret =~ /Unknown command:/m ) { MyErr->throw({err => $err, ret => $ret, execute => [ 'command1 +', 'expect1' ] ); } my ( $ret, $err ) = $ssh->execute( 'command2', 'expect2' ); if ( $err || $ret =~ /Unknown command:/m ) { MyErr->throw({err => $err, ret => $ret, execute => [ 'command2 +', 'expect2' ] ); } return; } { package MyErr; use Moo; with 'Throwable'; has err => (is => 'ro'); has ret => (is => 'ro'); has execute => (is => 'ro'); 1; }
        Well, I still find my approach more straight forward.
Re: exiting a subroutine neatly
by basiliscos (Pilgrim) on May 05, 2015 at 15:53 UTC

    Try to use Guard (there are alternatives, but I'm satisfied with that one). So, your code would look like:

    use Guard; # inside your routine ... my $guard = scope_guard { $ssh->exit_session; $ssh->close_session; } ... return 5;

    Please note, the scope guard will be executed even if your code dies somewhere after guard definition.

    WBR, basiliscos.
Re: exiting a subroutine neatly
by Anonymous Monk on May 05, 2015 at 09:35 UTC

    Is there a better way to execute the exit, close and then the return instead of writing them after every statement to get out of the subroutine ? kr

    No

    You could write <c> ... sshExitClose( $ssh ); return 2; ... sshExitClose( $ssh ); return 3; ... sub sshExitClose { $_[0]->exit_session; $_[0]->close_session; }

    But thats not exactly any better than the way you have it already

      No

      Create the following package:

      package SSHSentry; use strict; use warnings; sub new { my ($class, %params) = @_; my $self = bless {}, $class; foreach my $func (keys %params) { next unless $self->can($func); $self->$func($params{$func}); } return $self; } sub ssh_handle { my ($self, $handle) = @_; if (defined $handle and ref $handle eq 'Net::SSH') { # Assuming Ne +t::SSH is used for ssh handle. If not, change this. $self->{ssh_handle} = $handle; } return $self->{ssh_handle}; } sub DESTROY { my $self = shift; my $ssh = $self->ssh_handle() or return; $ssh->exit_session(); $ssh->close_session(); } 1;

      Now inside your function, do something like this: my $sentry = SSHSentry->new(ssh_handle => $ssh); Once $sentry goes out of scope, DESTROY is called and your cleanup happens.

      This type of package is convenient for wrapping around all sorts of stuff that can cause big problems if your code unexpectedly dies (semaphore control in a multi-threaded environment is a big one for this). In fact, it wouldn't surprise me if there is something a bit more thorough on CPAN.

Re: exiting a subroutine neatly
by locked_user sundialsvc4 (Abbot) on May 07, 2015 at 01:12 UTC

    Generically speaking, I think that this is an excellent opportunity to use “exceptions.”

    Basically, you arrange for some wrapper routine to take care of the protocols of initiating the SSH session, and of ending it.   “In the meantime, it calls your routine.”   If everything goes splendidly, your routine will return.   But, if “Private SNAFU was here,” your routine will instead throw an exception ... in Perl-speak, it will die.   And if this happens, the outer-level “wrapper” routine will catch that exception ... in Perl-speak, eval { }.   Either way, the outer-level routine is firmly in control.

    Thus, your handler-routine has a very simple job-description:   “Try your best to do what you’ve been asked to do, but if you find that you cannot do it, die.”

    And, the outer-level routine that called your handler has a simple job-description, too:   “Hope that the routine returns, but be prepared for the possibility that he won’t.   No matter what happens, see to it that the ssh session is properly initiated and terminated.”