[comp.lang.perl] gsh

merlyn@iwarp.intel.com (Randal Schwartz) (12/10/90)

In article <THOTH.90Dec9144127@reef.cis.ufl.edu>, thoth@reef (Gilligan) writes:
|   Whenever my fileserver goes down I lose mail, so I hacked up a perl
| script to execute commands in parallel and hand me the output in
| sequence.

Sounds like what I did.  And solving your exact problems too.  Here's
a script that I use hourly (at least... it's fired up from a cron job
for a background status check as one of its *many* uses aroundhere).

Shamelessly derived from 'gsh' in the Perl distribution, and even has
the same name...

You will need to change the stuff after <<'ENDHOSTLIST' unless by some
miracle you have exactly the same-named hosts as iWarp. :-)

Some example commands:

Set all the clocks to match up with r:
	gsh -v all-r 'rdate r'

Reboot the microvaxen:
	gsh -n15 -v -z15 vax 'reboot'

Invoke rdist to all the hosts from here:
	gsh -r -v all-`hostname` 'rdist -m $H'

See what the nfs params on a particular disk is:
	gsh sun grep iwarpq:/q /etc/fstab

Set a new root password:
	gsh -v -i all perl -pi.BAK - /etc/passwd <<EOF
	s#^root:[^:]+:#root:FoBS0d0PI8QRM:#;
EOF

Look at the df's for the diskful machines:
	gsh all-diskclient df

See... many uses.

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  gsh
# Wrapped by merlyn@iwarpse on Sun Dec  9 12:49:09 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'gsh' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'gsh'\"
else
echo shar: Extracting \"'gsh'\" \(8198 characters\)
sed "s/^X//" >'gsh' <<'END_OF_FILE'
X#!/local/usr/bin/perl
X## Copyright (C) 1989, 1990, by Randal L. Schwartz.  All Rights Reserved.
X## usage: gsh [options] hostspec [command [arg]...]
X## Runs command and args on hosts according to hostspec.  Results are
X## sent to STDOUT, with hostname prefix.  A missing command means to just
X## echo the computed hostnames on STDOUT. 'hostspec' is one of:
X##   hostname, hostattribute, hostspec+hostspec, hostspec-hostspec
X## Default hostlist is defined in @HOSTLIST later on.
X##
X## options:
X## -d: don't run any commands on other hosts... but fork anyway.
X## -h hostlist: extend the hostlist with the contents of the named file.
X## -H hostlist: replace the hostlist with the contents of the named file.
X## -i: give STDIN to the processes as their STDIN
X## -o place: send the outputs to "place$host" instead of STDOUT
X## -n procs: run this many processes at a time (default 5).
X##           (remember that each rsh is two processes on this host!)
X## -v: be noisy about starting and finishing processes.
X## -z sec: zap processes after sec seconds (default 300).
X## -r: don't run rsh, just invoke /bin/sh with $HOSTNAME and $H set to host
X##     "gsh -r -v all 'rdist -m $HOSTNAME'", for example
X
X@HOSTLIST = split(/\n/, <<'ENDHOSTLIST');  # comments allowed in here...
Xall=vax+sun
Xdiskful=vax+sun3server+sun4server+sun386
Xvax=microvax2
Xsun=sun3+sun4+sun386
Xsun3=sun3server+sun3client
Xsun4=sun4server+sun4client
Xsunserver=sun3server+sun4server
Xsunclient=sun3client+sun4client
Xsun3server=sun3/160s+sun3/260s+sun3/280s
Xsun3client=sun3/50c+sun3/60c+sun3/75c+sun3/140c
Xsun4server=sun4/280s+sun4/370s+sun4/390s+sun4/490s
Xsun4client=sun4/110c
Xsun386=sun386i
Xiwarpa iwa a microvax2 ultrix2
Xiwarpb iwb b microvax2 ultrix2
Xiwarpc iwc c microvax2 ultrix2
Xiwarpd iwd d microvax2 ultrix2
Xiwarpe iwe e microvax2 ultrix2
Xiwarpf iwf f microvax2 ultrix2
Xiwarpg iwg g microvax2 ultrix2
Xiwarph iwh h microvax2 ultrix2
Xiwarpi iwi i microvax2 ultrix2
Xiwarpj iwj j sun3/160s sunos4 diskserver exabyte
Xiwarpj0 iwj0 j0 sun3/75c sunos4 diskclient
Xiwarpj1 iwj1 j1 sun3/75c sunos4 diskclient
Xiwarpj2 iwj2 j2 sun3/75c sunos4 diskclient
Xiwarpj3 iwj3 j3 sun3/75c sunos4 diskclient
Xiwarpk iwk k sun3/260s sunos4 diskserver exabyte
Xiwarpk0 iwk0 k0 sun3/75c sunos4 diskclient
Xiwarpk1 iwk1 k1 sun3/75c sunos4 diskclient
Xiwarpk2 iwk2 k2 sun3/75c sunos4 diskclient
Xiwarpk3 iwk3 k3 sun3/75c sunos4 diskclient
Xiwarpl iwl l sun3/260s sunos4 diskserver exabyte
Xiwarpl0 iwl0 l0 sun3/75c sunos4 diskclient
Xiwarpl1 iwl1 l1 sun3/75c sunos4 diskclient
Xiwarpl2 iwl2 l2 sun3/75c sunos4 diskclient
Xiwarpl3 iwl3 l3 sun3/75c sunos4 diskclient
Xiwarpm iwm m sun3/260s sunos4 diskserver exabyte
Xiwarpm0 iwm0 m0 sun3/140c sunos4 diskclient
Xiwarpm1 iwm1 m1 sun3/140c sunos4 diskclient
Xiwarpm2 iwm2 m2 sun3/140c sunos4 diskclient
Xiwarpm3 iwm3 m3 sun3/140c sunos4 diskclient
Xiwarpn iwn n sun3/260s sunos4 diskserver exabyte
Xiwarpn0 iwn0 n0 sun3/140c sunos4 diskclient
Xiwarpn1 iwn1 n1 sun3/140c sunos4 diskclient
Xiwarpn2 iwn2 n2 sun3/140c sunos4 diskclient
Xiwarpn3 iwn3 n3 sun3/140c sunos4 diskclient
Xiwarpo iwo o sun3/260s sunos4 diskserver exabyte
Xiwarpo0 iwo0 o0 sun3/140c sunos4 diskclient
Xiwarpo1 iwo1 o1 sun3/140c sunos4 diskclient
Xiwarpo2 iwo2 o2 sun3/140c sunos4 diskclient
Xiwarpo3 iwo3 o3 sun3/140c sunos4 diskclient
Xiwarpp iwp p sun3/280s sunos4 exabyte
Xiwarpp0 iwp0 p0 sun386i sunos4
Xiwarpp1 iwp1 p1 sun386i sunos4
Xiwarpp2 iwp2 p2 sun386i sunos4
Xiwarpp3 iwp3 p3 sun386i sunos4
Xiwarpp4 iwp4 p4 sun386i sunos4
Xiwarpp5 iwp5 p5 sun386i sunos4
Xiwarpq iwq q sun4/280s sunos4 diskserver exabyte
Xiwarpr iwr r sun3/280s sunos4 diskserver exabyte
Xiwarpr0 iwr0 r0 sun3/60c sunos4 diskclient
Xiwarpr1 iwr1 r1 sun3/60c sunos4 diskclient
Xiwarpr2 iwr2 r2 sun3/60c sunos4 diskclient
Xiwarpr3 iwr3 r3 sun3/60c sunos4 diskclient
Xiwarpr4 iwr4 r4 sun3/60c sunos4 diskclient
Xiwarps iws s sun3/160s sunos4
X# iwarpsa iwsa sa sun4/390s sunos4 exabyte
Xiwarpsc iwsc sc sun4/390s sunos4 exabyte
Xiwarpsd iwsd sd sun4/390s sunos4 exabyte
Xiwarpse iwse se sun4/490s sunos4 exabyte
Xiwarpv iwv v microvax2 ultrix2
Xiwarpw iww w microvax2 ultrix2
Xiwarpx iwx x microvax2 ultrix2
Xiwarpy iwy y microvax2 ultrix2
Xiwarpz iwz z sun3/260s sunos4 diskserver exabyte
Xiwarpz0 iwz0 z0 sun3/60c sunos4 diskclient
Xiwarpz1 iwz1 z1 sun3/60c sunos4 diskclient
Xiwarpz2 iwz2 z2 sun3/60c sunos4 diskclient
Xiwarpz3 iwz3 z3 sun3/60c sunos4 diskclient
XENDHOSTLIST
X
X$| = 1; # don't buffer STDOUT
X
X$the_task_filename = "/tmp/$$.thetask";
X
X$tasks = 0;
X$taskmax = 5;
X$zapsecs = 300;
X
Xsub start {
X	local($host) = @_;
X
X	print "starting '$host'...\n" if $verbose;
X	
X	while ($tasks > 0 && $tasks >= $taskmax) {
X		&finish();
X	};
X	unless ($pid = fork) {	# child
X		open(STDIN, "<$the_task_filename") ||
X			die "Cannot open $the_task_filename as STDIN ($!)";
X		open(STDOUT, ">$place$host") ||
X			die "Cannot open $place$host ($!)";
X		open(STDERR, ">&STDOUT");
X		exec 'cat' if $debug;
X		$parent = $$;
X		if (fork) { # still the child
X			if ($dont_rsh) {
X				$ENV{'H'} = $host;
X				$ENV{'HOSTNAME'} = $host;
X				exec '/bin/sh';
X				die "Cannot exec /bin/sh ($!)";
X			} else {
X				exec 'rsh', $host, '/bin/sh';
X				die "Cannot exec rsh ($!)";
X			}
X		}
X		# child child
X		$zaptime = time + $zapsecs;
X		while (time < $zaptime) {
X			sleep 5;
X			exit 0 if getppid == 1;
X		}
X		kill 9, $parent;
X		print "\nTIMED OUT AFTER $zapsecs SECONDS\n";
X		exit 0;
X	}
X	$tasklist{$pid} = $host;
X	$tasks++;
X}
X
Xsub finish {
X	return unless $tasks > 0;
X	print "waiting on '", join(" ", sort values(tasklist)), "'...\n"
X		if $verbose;
X	do {
X		die "Nothing to wait for??? ($!)" unless ($pid = wait) > 0;
X	} until $tasklist{$pid};
X	print "finished task on '", delete $tasklist{$pid}, "'.\n"
X		if $verbose;
X	$tasks--;
X}
X
Xsub finishall {
X	while ($tasks > 0) {
X		&finish();
X	}
X}
X
Xsub gethostlist {
X	local($f,$replace) = @_;
X	open(GETHOSTLIST, "<$f") || die "Cannot open '$f' ($!)";
X	@HOSTLIST = () if $replace;
X	unshift(@HOSTLIST, <GETHOSTLIST>); # put it at the beginning
X	close(GETHOSTLIST);
X}
X
X# end initialization... begin code...
X
Xwhile ($ARGV[0] =~ /^-/) {
X	$_ = shift;
X	$debug++, $verbose++, next if /^-d/;
X	$verbose++, next if /^-v/;
X	$taskmax = (length($_) ? $_ : shift), next if s/^-n//;
X	&gethostlist(length($_) ? $_ : shift, 1), next if s/^-H//;
X	&gethostlist(length($_) ? $_ : shift, 0), next if s/^-h//;
X	$do_stdin++, next if /^-i/;
X	$place = (length($_) ? $_ : shift), next if s/^-o//;
X	$zapsecs = (length($_) ? $_ : shift), next if s/^-z//;
X	$dont_rsh++, next if /^-r/;
X	die "unknown flag $_";
X}
X
X$place = "/tmp/$$.", $do_stdout++ unless $place;
X
Xunshift(@HOSTLIST,"TARGET=" . shift);
X
X$the_task .= join(" ", @ARGV);
Xif ($do_stdin) {
X	$_ = join("",<STDIN>);
X	chop if /\n$/;
X	$the_task = "($the_task ;) <<'FoObAr'\n$_\nFoObAr\n";
X	# if I got tricky, I could skip the extra shell, but, hey... it works
X}
X
X@TARGETS = ();
X
X$attr{'TARGET'} = 1;	# this is what I want.
X
Xfor $_ (@HOSTLIST) {
X	s/\s*\n?$//;	# toss trailing white
X	s/^\s*//;	# toss leading white
X	next if /^(#.*)?$/; # skip comment lines and blank lines
X	if (/^([^-+=]+)=(.*)/) {
X		($name,$repl) = ($1,"+$2");
X		next unless $yes = $attr{$name}; # +1 if wanted, -1 if not
X		while ($repl =~ s/^([+-])([^-+]+)//) {
X			next if $attr{$2};
X			$attr{$2} = ($1 eq '-') ? - $yes : $yes;
X			print "assigning $attr{$2} to $2\n" if $debug;
X		}
X	} else {	# must be a terminal node:
X		@attr = split;
X		$host = $attr[0];
X		$wanted = 0;
X		for $attr (@attr) {
X			$wanted++, next if $attr{$attr} > 0;
X			$wanted=-1, last if $attr{$attr} < 0;
X		}
X		push(TARGETS, $host) if $wanted > 0;
X	}
X}
X
Xif ($the_task =~ /^\s*$/) { # no command?  just list the hosts
X	print join("\n", @TARGETS), "\n";
X	exit 0;
X}
X
Xopen(THE_TASK, ">$the_task_filename") || die "Cannot open THE_TASK ($!)";
Xprint THE_TASK $the_task;
Xclose(THE_TASK);
X
Xfor $host (@TARGETS) {	# launch'em all, $taskmax at a time
X	&start($host);
X}
X
X&finishall();		# and hang out while the last $taskmax finish
X
Xunlink $the_task_filename; # no need for this anymore
X
Xexit 0 unless $do_stdout;
X
Xfor $host (@TARGETS) {	# show what they said
X	(warn "missing output for $host ($!)"), next
X		unless open(F,"<$place$host");
X	if ($_ = join("$host:\t", <F>)) {
X		print "$host:\t$_";
X		print "\n" unless /\n$/;
X	}
X	close(F);
X	unlink "$place$host";
X}
Xexit 0;
END_OF_FILE
if test 8198 -ne `wc -c <'gsh'`; then
    echo shar: \"'gsh'\" unpacked with wrong size!
fi
chmod +x 'gsh'
# end of 'gsh'
fi
echo shar: End of shell archive.
exit 0

print "Just another Perl hacker,"
-- 
/=Randal L. Schwartz, Stonehenge Consulting Services (503)777-0095 ==========\
| on contract to Intel's iWarp project, Beaverton, Oregon, USA, Sol III      |
| merlyn@iwarp.intel.com ...!any-MX-mailer-like-uunet!iwarp.intel.com!merlyn |
\=Cute Quote: "Intel: putting the 'backward' in 'backward compatible'..."====/