--- /dev/null
+#!/usr/bin/perl -w
+use strict qw(vars refs);
+use IO::Handle;
+our $self= 'innduct-forall';
+our $deftable= '/etc/news/ducts.table';
+
+our $usage=<<END;
+usage:
+ $self [<ducts-table>] <pattern>
+where <pattern> is a sh command and may contain
+ !site site name
+ !fqdn fqdn or empty string
+ !opts innduct options
+ !duct shorthand for !opts !site !fqdn
+ !! single !
+default ducts-table is $deftable
+END
+
+
+die "$self: bad usage\n$usage" unless
+ @ARGV>=1 && @ARGV<=2 && $ARGV[0] !~ m/^\-/;
+
+our $table= (@ARGV>1 ? shift @ARGV : $deftable);
+our $pattern= shift @ARGV;
+
+our $maxexit= 0;
+
+open T, '<', $table or die "$self: $table: $!\n";
+while (<T>) {
+ s/^\s*//; s/\s+$//;
+ next unless m/^[^\#]/;
+ my @v= split /\s+/, $_, 3;
+ my %c;
+ foreach my $f (qw(site fqdn opts)) {
+ my $v= shift @v;
+ $v='' unless defined $v;
+ $c{$f}= $v;
+ }
+ $c{'fqdn'}= $c{'site'} if $c{'fqdn'} eq '=';
+ $c{'duct'}= "$c{opts} $c{site} $c{fqdn}";
+ $c{'!'}= '!';
+ $_= $pattern;
+ s/\!(\w+|\!)/
+ my $v= $c{$1};
+ die "$self: unknown substitution $1\n" unless defined $v;
+ $v;
+ /ge;
+ my $r= system $_;
+ die "$self: run command: $!\n" if $r<0;
+ warn "$self: $c{site}: wait status $r: $_\n" if $r>0;
+ $r |= 128 if $r>0 && $r<128;
+ $r>>=8 if $r>=256;
+ $maxexit=$r if $r>$maxexit;
+}
+die "$self: $table: read: $!\n" if T->error;
+close T or die "$self: table: close: $!\n";
+
+exit $maxexit;