Skip to content

Instantly share code, notes, and snippets.

@leverich
Created September 19, 2015 20:16
Show Gist options
  • Save leverich/9a22c35a9f7884af2e7b to your computer and use it in GitHub Desktop.
Save leverich/9a22c35a9f7884af2e7b to your computer and use it in GitHub Desktop.
Yet another parallel ssh wrapper
#!/usr/bin/perl
%hosts = (
"example" => "host-0..9",
);
sub usage {
print"
dsh -- run commands with ssh on several hosts simultaneous
Usage: $0 [-vdnsph] <hostlist> <commands>
-p|--parallel -s|--sequential -h|--help
-v|--verbose -d|--dryrun -n|--showname
hostlist is a comma-separated list of hosts on which to run commands.
The string '..' can be used to denote numerical ranges.
The following host aliases are defined:
";
for (sort keys %hosts) { printf "% 15s : %s\n", $_, $hosts{$_}; }
print "
Example usage: dsh -np example hostname
";
exit shift;
}
use Getopt::Long;
Getopt::Long::Configure("bundling", "require_order");
GetOptions ("v|verbose" => \$verbose,
"d|dryrun" => \$dryrun,
"n|showname" => \$showname,
"s|sequential" => \$sequential,
"p|parallel" => \$parallel,
"h|help" => \$help) or usage 1; # die $!;
$sequential = 1 if !$parallel;
$verbose = 1 if $dryrun;
usage 0 if $help;
sub load_hosts {
my $hostsref = shift;
my $path = "$ENV{HOME}/.dsh";
return unless -d $path;
opendir DIR, $path or die $!;
for my $file (readdir DIR) {
next unless -f "$path/$file";
next if $file =~ /(,|\.\.)/; # so we don't screw up resolve_hosts()
open FILE, "<$path/$file" or die $!;
my @entries = ();
while (<FILE>) {
s/\#.*//; # strip comments
s/^\s*//; # strip leading whitespace
s/\s*$//; # strip trailing whitespace
s/,/ /g; # convert commas to a space
s/\s+/,/g; # convert spaces to a single comma
next unless /./; # skip blank lines...
print "consider $_\n";
push @entries, $_;
}
close FILE;
$hostsref->{$file} = join(",", @entries); # save
}
closedir DIR;
}
sub resolve_hosts {
my $root = shift;
my @parts = split(/\s*,\s*/, $root);
my @hosts = ();
for my $part (@parts) {
## case 1: needs to expand %hosts
## case 2: needs to expand ..
## case 3: no expansion needed
if (exists $hosts{$part}) {
push @hosts, resolve_hosts($hosts{$part});
} elsif ($part =~ /^(.*?)(\d+)\.\.(\d+)(.*?)$/) {
next if $2 < 0 or $3 > 1000 or !$1;
for ($2..$3) {
push @hosts, "$1$_$4";
}
} else {
push @hosts, $part;
}
}
return @hosts;
}
load_hosts(\%hosts);
$hosts = shift @ARGV;
@hosts = resolve_hosts($hosts);
## remove duplicates
@uniq_hosts = ();
%seen = ();
for my $host (@hosts) {
push @uniq_hosts, $host unless defined $seen{$host};
$seen{$host} = 1;
}
@hosts = @uniq_hosts;
##
usage 1 unless scalar @hosts;
die "No command specified" unless scalar @ARGV;
sub ssh {
my $host = shift;
my @args = ("-o", "StrictHostKeyChecking=no",
"-o", "ForwardX11=no",
"-o", "ForwardX11Trusted=no",
"-o", "ForwardAgent=no",
"-o", "BatchMode=yes",
"-o", "ConnectTimeout=5", $host, @_);
push (@args, "2>&1|sed 's/^/$host: /'") if $showname;
print STDERR join(" ", "ssh", @args), "\n" if $verbose;
my $ret = system { "ssh" } "ssh", @args;
}
sub run {
my $host = shift;
if ($sequential) {
ssh($host, @_);
} else {
my $pid = fork;
if ($pid == 0) {
ssh($host, @_);
exit 0;
}
}
}
print STDERR "Hostlist: " . join(" ", @hosts) . "\n" if $verbose;
for my $host (@hosts) {
run($host, @ARGV);
}
while (wait() != -1) {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment