Skip to content
Perl and SSH presentation at YAPC::Europe 2013
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.

Perl and SSH

Salvador Fandiño (

YAPC::Europe 2013

How do you automate SSH tasks?

If you use ...

  • system("ssh $host $cmd @args")
  • or backticks
  • Net::SSH
  • Net::SSH::Perl
  • Expect
  • Net::SSH::Expect are living in the past!

Using system or backticks

  • ok for simple things
  • but very inneficient, opens a new SSH connection every time
  • no password authentication
  • you have to properly quote the arguments
  • sometimes twice
  • or be insecure


  • simple wrapper for the ssh command
  • provides also sshopen2 and sshopen3
  • has same problems as using system


  • huge module
  • at its time it represented a great effort
  • I guess most of it is a direct translation of OpenSSH C code
  • but today, nobody maintains it
  • lots of known bugs
  • very difficult to install (Math::Pari)
  • API good for simple things, bad for advanced things
  • supports SFTP via Net::SFTP, doesn't support SCP


  • Expect is a great module for automating interactive programs
  • It's a good way to automate ssh password authentication
  • After that, it is (usually) the wrong tool
  • It talks to the remote shell, you don't want that!
  • On some systems (AIX, HP-UX) ttys may drop data


my $pty = IO::Pty->new;
my $expect = Expect->init($pty);
my $pid = open2($in, $out, '-');
unless ($pid) {
  defined $pid or die "unable to fork";
  do { exec @ssh_cmd };
  exit -1;


  • Builds on top of Expect
  • On the wrong way
  • It talks to the shell
  • It is not reliable

So, what should I use?

Modern SSH modules

  • Net::SSH2
  • Net::OpenSSH
  • Net::SSH::Any

Well, actually...

  • Net::SSH2
  • Net::OpenSSH
  • Net::SSH::Any
  • Net::SSH::Mechanize
  • Net::OpenSSH::Parallel
  • Net::OpenSSH::Compat
  • Net::SFTP::Foreign
  • ...



  • wrapper for the libssh2 C library
  • quite portable
  • quite easy to install on Unix/Linux, almost easy to install on Windows, don't known about VMS
  • it is a very thin wrapper
  • efficient, can run several commands over one SSH connection
  • C'ish low level API:
    • very simple things are easy to do
    • not so simple things become quite hard
  • supports SCP
  • very primitive and inneficient support for SFTP
  • project started by David B. Robins, currently being actively maintained by Rafael Kitover

Net::SSH2 - usage

use Net::SSH2;

my $ssh2 = Net::SSH2->new();

$ssh2->connect('') or die $!;

if ($ssh2->auth_keyboard('fizban')) {
    my $chan = $ssh2->channel();

Net::SSH2 problems

  • very low level C'ish API: not for lazy people
  • libssh2 is not a mature project yet
  • requires a C compiler



  • wrapper around OpenSSH ssh
  • uses its connection multiplexing feature
    • several commands can be run over the same connection
    • efficient
  • Perlish API with lots of belts and whistles
  • easy to use, complex things are almost easy
  • can work asynchronously
  • supports SFTP, SCP, rsync and sshfs
  • automatic argument quoting

Net::OpenSSH - usage

use Net::OpenSSH;
my $ssh = Net::OpenSSH->new($host, user => $user, password => $password);
$ssh->die_on_error("unable to connect");

my @output = $ssh->capture("cat /etc/passwd");

my ($out, $err) = $ssh->capture2("find /");

$ssh->system({stdin_data => "hello\n"},
             "cat >>log");

my $pid = $ssh->spawn({stderr_to_stdout => 1,
                       stdout_file => "tar.log"},
                      'tar', 'cf', '/tmp/my backup.tar', '/home/me');

$ssh->scp_get('/tmp/*.tar', '.');

$ssh->rsync_put({verbose => 1, safe_links => 1},
                "etc", "/etc");

my $sftp = $ssh->sftp; # return a Net::SFTP::Foreign object
my $ls = $sftp->ls;

Net::OpenSSH argument quoting


my $out = $ssh->capture("ls /etc"); # no quoting
my $out = $ssh->capture(ls => '~/My Documents'); # quoting
my $out = $ssh->capture({quote_args => 0},
                        ls => '~/My Documents'); # no quoting

# selectively quoting arguments:
my $out = $ssh->capture(ls => \'~/My Documents/*.pdf');
        # lets file name wildcards be expanded by the remote shell

my $out = $ssh->capture(ls => \\'~/My Documents/*.pdf'); # wrong!

my $out = $ssh->capture(@cmd1, \\'&&', @cmd2, \\'2>/dev/null');

Net::OpenSSH argument quoting

  • On the stable release, argument quoting expects a POSIX compatible shell on the remote side

  • The development release has support for different quoting backends - POSIX (i.e. ksh, bash) and csh already there - maybe Windows/DOS backend in the future

Net::OpenSSH problems

  • Net::OpenSSH does not work on Windows
    • OpenSSH multiplexing feature does not work on Windows, not even under cygwin
  • Requires the OpenSSH ssh command



  • API very similar to Net::OpenSSH

  • works on top of

    • Net::SSH2
    • Net::OpenSSH
    • maybe Net::SSH::Perl in the future
    • maybe simple wrapper around native ssh
  • supports SCP and SFTP for file transfers, efficiently

  • a work in progress

  • though, basic functionality is already stable



  • It uses the AnyEvent framework
  • It aims to support sudoing smoothly
  • Limited API
  • It talks to the remote shell, unreliable!



  • Run commands in parallel in remote hosts through SSH
  • Build on top of Net::OpenSSH
  • Declarative API:
    • tell the module all the actions you want to perform on the remote hosts
    • let the module take care of everything and do the tasks
    • handle possible errors

Net::OpenSSH::Parallel - usage

my $pssh = Net::OpenSSH::Parallel->new;

# tell the object what the remote hosts are:
$pssh->add_host('host1', user => foo, password => $pwd);
$pssh->add_host('host2', user => foo, password => $pwd);

# declare the actions you want to run:
$pssh->push('host1', command => "echo hello from host1");
$pssh->push('host2', command => "echo hello from host2");

# for several host in one call:
$pssh->push('host*', command => "echo hello from some host");

# with variable expansion:
$pssh->push('host*', command => "echo hello from host %HOST%");

# and run it:

Net::OpenSSH::Parallel - usage

# other actions:
$pssh->push('*', scp_get => '/var/log/messages.0', 'logs/messages.0.%HOST%');

$pssh->push('*', rsync_put => '/var/www', '/var/www');

# can pass extra arguments to the underlaying Net::OpenSSH methods
$pssh->push('*', rsync_put => { safe_links => 1,
                                stdout_file => ['>>', 'mylog'],
                                stderr_to_stdout => 1 },
			  '/var/www', '/var/www');

# runs a custom sub on a locally forked process
$pssh->push('*', parsub => \&my_sub);

Net::OpenSSH::Parallel - Expect and sudo

sub sudo_install {
    my ($label, $ssh, @pkgs) = @_;
    my ($pty) = $ssh->open2pty('sudo', 'apt-get', 'install', @pkgs);
    my $expect = Expect->init($pty);
    $expect->expect($timeout, ":");
    $expect->expect($timeout, "\n");
    while(<$expect>) { print };
    close $expect;

$pssh->push('*', parsub => \&sudo_install, 'scummvm');

Net::OpenSSH::Parallel - dependencies

$pssh->push('dmz*', scp_get => '/etc/passwd', 'passwd.%HOST%');
$pssh->push('safe', join => 'dmz*');
$pssh->push('safe', command => mkdir, "/var/safe/$date");
$pssh->push('safe', scp_put => 'passwd.*', "/var/safe/$date");

Net::OpenSSH::Parallel problems

  • Unable to distribute tasks around a set of hosts
  • Can't run more than one command per host simultaneously
  • stdin/stdout/stderr has to go to the file system, no pipes between tasks



  • Implements most of Net::SSH::Perl and Net::SSH2 APIs on top of Net::OpenSSH
  • Because sometimes people has problems installing them


On the works...

Other interesting modules

  • SSH::Batch
  • App::MrShell
  • POE::Component::OpenSSH
  • GRID::Machine
  • IPC::PerlSSH
  • (R)?ex
  • ...


Thank you!


You can’t perform that action at this time.