added files
[bcm963xx.git] / userapps / opensource / net-snmp / perl / AnyData_SNMP / netsh
1 #!/usr/bin/perl
2
3 use Getopt::Std;
4
5 use DBI;
6 use Term::ReadLine;
7 use SNMP;
8 use AutoLoader;
9 use IO::File;
10
11 BEGIN {
12     $opts{'t'} = ! eval { require Text::FormatTable; };
13     $ansicolor = eval { require Term::ANSIColor; };
14 }
15
16 # override some of the functions to force our colorized semantics.
17 # This is really ugly, but if you pass colorized strings directly to
18 # Text::FormatTable then it calculates the string lengths incorrectly.
19 # Children: don't try this at home.  We're trained professionals.
20 if ($colorize) {
21    eval {
22         #
23         # colorized strings.
24         #
25         package color_string;
26
27         require Term::ANSIColor;
28
29         use overload
30             '""' => \&string_it
31         ;
32
33         sub string_it {
34             if ($_[0][3]) {
35                 if ($_[0][3] == 1) {
36                     return color_it();
37                 } else {
38                     $_[0][3] -= 1;
39                     return $_[0][1];
40                 }
41             }
42             return $_[0][1];
43         }
44
45         sub colorize_next {
46             $_[0][3] = 2;
47         }
48
49         sub color_it {
50             my $str = $_[1] || $_[0][1];
51             return $_[0][0] . $str . $_[0][2];
52         }
53
54         sub new {
55             my $this = [Term::ANSIColor::color($_[2]), $_[1],
56                       Term::ANSIColor::color('reset')];
57             return bless($this, $_[0]);
58         }
59     }
60 }
61
62 if ($opts{'t'} == 0 && $colorize) {
63     eval {
64         package Text::FormatTable;
65
66         sub _l_box($$)
67         {
68             my ($width, $text) = @_;
69             my $lines = _wrap($width, $text);
70             map { 
71                 if (ref($text) eq "color_string") {
72                     $_ .= $text->color_it($_) . ' 'x($width-length($_));
73                 } else {
74                     $_ .= ' 'x($width-length($_));
75                 } 
76             } @$lines;
77             return $lines;
78         }
79
80         sub _r_box($$)
81         {
82             my ($width, $text) = @_;
83             my $lines = _wrap($width, $text);
84             map { 
85                 if (ref($text) eq "color_string") {
86                     $_ = ' 'x($width-length($_)) . $text->color_it($_);
87                 } else {
88                     $_ = ' 'x($width-length($_)) . $_;
89                 } 
90             } @$lines;
91             return $lines;
92         }
93
94     }
95 }
96
97 if (!$ansicolor) {
98     $begin = $end = "*";
99 }
100
101 $SNMP::use_enums=1;
102
103
104 #defaults
105 $opts{'d'} = "\t";
106 $opts{'v'} = 1;
107 $opts{'l'} = 'authNoPriv';
108 $params = "";
109
110 getopts('hd:tR:u:l:v:a:A:x:X:p:c:t:r:',\%opts);
111
112 usage() if ($#ARGV == -1 || $opts{'h'});
113
114 my %parammap = {
115     'v' => 'Version',
116     'u' => 'SecName',
117     'a' => 'AuthProto',
118     'A' => 'AuthPass',
119     'x' => 'PrivProto',
120     'X' => 'PrivPass',
121     'p' => 'RemotePort',
122     't' => 'Timeout',
123     'r' => 'Retries',
124     'c' => 'Community',
125     'l' => 'SecLevel'
126     };
127
128 foreach my $x (keys(%opts)) {
129     if ($parammap{$x}) {
130         $params .= ";ad_SNMP_$parammap{$x}=$x";
131     }
132     push @sessparams,$parammap{$x},$x;
133 }
134
135 my $host = shift @ARGV;
136 $params .= ";ad_SNMP_DestHost=" . $host;
137 push @sessparms,'DestHost', $host;
138
139 # connect to the DBI interface
140 $AnyData::Storage::SNMP::debugre = $opts{'R'} if ($opts{'R'});
141 ($dbh = DBI->connect("dbi:AnyData:ad_default=SNMP$params"))
142     || die "\tConnection problem: $DBI::errstr\n";
143 $AnyData::Storage::SNMP::debugre = $opts{'R'} if ($opts{'R'});
144
145 $prompt = "netsh> ";
146
147 load_rcs();
148
149 # setup terminal prompter
150 $ENV{'PERL_RL'}='o=0' if (!exists($ENV{'PERL_RL'}));
151 # the ornaments are too ugly
152 $term = new Term::ReadLine 'netsh';
153
154 if ($#ARGV >= 0) {
155     # command line command
156     netsh(join(" ",@ARGV));
157 } else {
158     # interactive shell
159     while($cmd = $term->readline($prompt)) {
160         last if ($cmd eq "exit" || $cmd eq "quit" || $cmd eq "q");
161         netsh($cmd, \%conf);
162     }
163 }
164
165 # load all configuration files we can find.
166 sub load_rcs {
167     if (-f "$ENV{'HOME'}/.snmp/netshrc") {
168         source_file("$ENV{'HOME'}/.snmp/netshrc");
169     }
170     if (-d "$ENV{'HOME'}/.snmp/netsh") {
171         foreach my $i (glob("$ENV{'HOME'}/.snmp/netsh/*")) {
172             if (-f "$i" && "$i" !~ /.*(~|.bak)$/) {
173                 source_file("$i");
174             }
175         }
176     }
177 }
178
179 # command definition for sourcing a particular file
180 sub source_file {
181     my $fh = new IO::File;
182     if ($fh->open("<$_[0]")) {
183         while(<$fh>) {
184             if (s/<<\s*(\w+)$//) {
185                 my $stopat = $1;
186                 my $lines = $_;
187                 while(<$fh>) {
188                     last if (/$stopat/);
189                     $lines .= $_;
190                 }
191                 $_ = $lines;
192             }
193             netsh($_);
194         }
195     } else {
196         print STDERR "no such file: $_[0]\n";
197     }
198 }
199
200 # export data into an external file
201 sub my_export {
202     shift;
203     if (!$insh) {
204         my $cmd = "create table exporttable (" . join (" varchar(255), ",@{$sth->{NAME}}) . " varchar(255))";
205         $exporth->do($cmd);
206         $cmd = "insert into exporttable values(" . ('?, ' x ($#_)) . "?)";
207         $insh = $exporth->prepare($cmd);
208     }
209     $insh->execute(@_);
210 }
211
212 # the main processing function.
213 sub netsh {
214     my $stmt = shift;
215     chomp($stmt);      # remove trailing white space
216     $stmt =~ s/^\s+//; # remove leading white space
217     $stmt =~ s/;*$//;  # get rid of trailing semicolons
218     return if ($stmt =~ /^\s*$/);
219     return if ($stmt =~ /^\s*\#/);
220     my ($name, $args) = ($stmt =~ /^(\w+)\s*(.*)$/);
221
222     #define alias
223 #    print "doing [$multi_alias]: $stmt\n";
224     if ($name eq "alias" || $multi_alias) {
225         if ($multi_alias) {
226             if ($stmt =~ /^prompt\s+(\d+)\s+[\"\'](.+)[\"\']/) {
227                 $aliases{$current_alias}{'prompts'}[$1] = $2;
228                 return;
229             } elsif ($stmt =~ /^prompt\s+(\d+)\s+(.+)/) {
230                 my $x = $2;
231                 my $spot = $1;
232                 $x =~ s/\s+$//;
233                 $aliases{$current_alias}{'prompts'}[$spot] = "$x ";
234                 return;
235             } elsif ($stmt =~ /^\s*\}\s*$/) {
236                 $prompt = $oprompt;
237                 $multi_alias = 0;
238                 return;
239             }
240             push @{$aliases{$current_alias}{'definition'}},$stmt;
241             return;
242         }
243         $stmt =~ s/^alias\s+//;
244         if ($args eq "") {
245             foreach $i (sort keys(%aliases)) {
246                 display_alias($i);
247             }
248             return;
249         }
250         ($name, $args) = ($stmt =~ /^(\w+)\s*(.*)$/);
251         if ($args eq "") {
252             display_alias($name);
253             return;
254         }
255 #       print "alias: $name $args\n";
256         if ($args eq "{") {
257             $oprompt = $prompt;
258             $prompt = "define $name> ";
259             $current_alias = $name;
260             $multi_alias = 1;
261             $aliases{$name}{'definition'} = [];
262             return;
263         }
264         $aliases{$name}{'definition'} = $args;
265         return;
266     }
267
268     #eval
269     if ($name eq "eval") {
270         eval $args;
271         return;
272     }
273
274     #eval just vars
275     if ($name eq "evalvars") {
276 #       print "args1:",$args,"\n";
277         $args =~ s/\$(\w+)/$$1/eg;
278 #       print "args2:",$args,"\n";
279         netsh($args);
280         return;
281     }
282
283     #eval aliases
284     while (exists $aliases{$name}) {
285 #       print "modified: $stmt -> ";
286         my @ARGS = split(/\s+/,$args);
287         my $statements;
288         if (ref($aliases{$name}{'definition'}) eq "ARRAY") {
289             $statements = $aliases{$name}{'definition'};
290
291             # maybe prompt for values
292             if ($#{$aliases{$name}{'prompts'}} > -1) {
293                 my $i;
294                 for($i = 1; $i <= $#{$aliases{$name}{'prompts'}}; $i++) {
295                     if (!$ARGS[$i-1] && $term) {
296                         $ARGS[$i-1] = 
297                             $term->readline($aliases{$name}{'prompts'}[$i]);
298                     }
299                 }
300             }
301         } else {
302             $statements = [$aliases{$name}{'definition'}];
303         }
304         foreach my $stmt (@$statements) {
305             #print "$stmt -> ";
306             $stmt =~ s/\\(\d+)/$ARGS[$1-1]/g;
307 #           print "running $stmt\n";
308             ($name, $args) = ($stmt =~ /^(\w+)\s*(.*)$/);
309             netsh($stmt);
310         }
311         return;
312     }
313
314     if ($stmt =~ /^rehash$/) {
315         load_rcs();
316         return;
317     }
318
319     my $subfn;
320
321     if ($stmt =~ /^eval (.*)/) {
322         eval $1;
323     }
324
325     if ($stmt =~ s/^printf\s+(\".*\")\s*(.*)/$2/) {
326         if ($2 eq "") {
327             print eval $1;
328             return;
329         }
330         $subfn = \&my_printf;
331         $stmt = $2;
332         $printfmt = $1;
333     }
334
335     # special show columns statement
336     if ($stmt =~ /^show columns from (\w+)$/) {
337         my $mibnode = $SNMP::MIB{$1};
338         my $entrynode = $mibnode->{children}[0];
339         if (!defined($mibnode) || !defined($entrynode)) {
340             print STDERR "no such table: $1\n";
341             return;
342         }
343         if ($opts{'t'}) {
344             map { print $_->{label},"\n"; } sort { $a->{subID} <=> $b->{subID}} @{$entrynode->{children}};
345         } else {
346             $table = Text::FormatTable->new('|r|');
347             $table->rule('-');
348             $table->head('Column');
349             $table->rule('-');
350             map { $table->row($_->{label}); } sort { $a->{subID} <=> $b->{subID}} @{$entrynode->{children}};
351             $table->rule('-');
352             print $table->render();
353         }
354         return;
355     }
356
357     if ($stmt =~ /^source\s+(.*)/) {
358         source_file($1);
359         return;
360     }
361
362     if ($stmt =~ s/^export\s+(\S+)\s+(.*)/$2/) {
363         $insh = undef;
364         unlink($1);
365         $exporth = DBI->connect('dbi:AnyData:');
366         $exporth->func('exporttable','CSV',$1,'ad_catalog');
367         $subfn = \&my_export;
368     }
369
370     if ($stmt =~ /^import\s+(\S+)/) {
371         my $exporth = DBI->connect('dbi:AnyData:');
372         $exporth->func('exporttable','CSV',$1,'ad_catalog');
373         my $selh = $exporth->prepare("select * from exporttable");
374         $selh->execute();
375         $old_data = [];
376         while(my $row = $selh->fetchrow_arrayref) {
377             push @$old_data, @$row;
378         }
379         $selh->finish();
380         $exporth->disconnect();
381         return;
382     }
383     
384     if ($stmt =~ /^diff\s+(.*)/) {
385         $running_watch = 2;
386         netsh($1);
387         $running_watch = 0;
388         return;
389     }
390
391     if ($stmt =~ /^watch\s+(.*)/) {
392         $running_watch = 1;
393         my $cmd = $1;
394         my $delay = 1;
395         my $clear = `clear`;
396         if ($cmd =~ s/^(\d+)\s+(.*)/$2/) {
397             $delay = $1;
398             $cmd = $2;
399         }
400         $SIG{'TERM'} = sub { $running_watch = 0; };
401         $SIG{'INT'} = sub { $running_watch = 0; };
402         while($running_watch) {
403             print $clear;
404             netsh($cmd);
405             sleep($delay);
406         }
407         $SIG{'TERM'} = \&exit;
408         $SIG{'INT'} = \&exit;
409         return;
410     }
411
412     # we have an SQL statement.  process it.
413     if ($stmt =~ /^(select|insert|update|delete)/) {
414         $sth = $dbh->prepare($stmt);
415         $sth->execute();
416         if ($stmt =~ /^select/) {
417             my $table;
418             my $older_data = $old_data;
419             if ($running_watch == 1) {
420                 $old_data = [];
421             }
422             my $oldcount = 0;
423             while($row = $sth->fetchrow_arrayref) {
424                 
425                 if ($running_watch) {
426                     $newrow=[];
427                     my $count;
428                     for($count = 0; $count <= $#$row; $count++) {
429                         if ($older_data &&
430                             $row->[$count] ne
431                             $older_data->[$oldcount][$count]) {
432                             if ($ansicolor) {
433                                 push @$newrow, new color_string($row->[$count],'red');
434                             } else {
435                                 push @$newrow,"$begin$row->[$count]$end";
436                             }
437                         } else {
438                             push @$newrow,$row->[$count];
439                         }
440                         if ($running_watch == 1) {
441                             $old_data->[$oldcount][$count] = $row->[$count];
442                         }
443                     }
444                     $oldcount++;
445                     $row = $newrow;
446                 }
447
448                 # self printing;
449                 if (ref($subfn) eq "CODE") {
450                     &$subfn($printfmt,@$row);
451                     next;
452                 }
453
454                 if ($opts{'t'}) {
455                     if ($opts{'d'} eq 'xml') {
456                         print "  <row>\n";
457                         for(my $xx = 0; $xx < $#{$sth->{NAME}}; $xx++) {
458                             print "    <$sth->{NAME}[$xx]>$row->[$xx]</$sth->{NAME}[$xx]>\n";
459                         }
460                         print "  </row>\n";
461                     } elsif ($opts{'d'} eq 'xmlshort') {
462                         print "  <row ";
463                         for(my $xx = 0; $xx < $#{$sth->{NAME}}; $xx++) {
464                             print " $sth->{NAME}[$xx]=\"$row->[$xx]\"";
465                         }
466                         print "/>\n";
467                     } else {
468                         print join($opts{'d'},@$row),"\n";
469                     }
470                 } elsif (!$table) {
471                     $table = Text::FormatTable->new('|r' x ($#$row+1) . "|");
472                     $table->rule('-');
473                     $table->head(@{$sth->{NAME}});
474                     $table->rule('-');
475                     $table->row(@$row);
476                 } else {
477                     $table->row(@$row);
478                 }
479             }
480             if ($table) {
481                 $table->rule('-');
482                 print $table->render();
483             }
484         }
485         $sth->finish();
486         return;
487     }
488
489     # retrieve just one variable and display it
490     if ($stmt =~ /^(get|)\s*([^\s]+)\s*[^=]/) {
491         my $sess = make_session();
492         if ($sess) {
493             my @results = split(/[,\s]+/,$stmt);
494             my $resultsv = new SNMP::VarList();
495             # expression stolen from the main perl SNMP module
496             map {  my ($tag, $iid) = 
497                        (/^((?:\.\d+)+|(?:\w+(?:\-*\w+)+))\.?(.*)$/);
498                    push @$resultsv, [$tag, $iid] } @results;
499             $sess->get($resultsv)  || 
500                 print STDERR "Error: $sess->{ErrorNum} $sess->{ErrString}\n";
501             @results = ();
502             map { push @results, $_->[2] } @$resultsv;
503             if (ref($subfn) eq "CODE") {
504                 &$subfn($printfmt,@results);
505             } else {
506                 print join(" ", @results),"\n";
507             }
508         } else {
509             print STDERR "could not establish a SNMP session to $host\n";
510         }
511         return;
512     }
513
514     # set something
515     if ($stmt =~ /^(set|)\s*([^\s]+)\s=\s(.*)$/) {
516         my $sess = make_session();
517         if ($sess) {
518             $sess->set([$2,undef,$1]) || 
519                 print STDERR "opps: $sess->{ErrorNum} $sess->{ErrString}\n";
520         } else {
521             print STDERR "could not establish a SNMP session to $host\n";
522         }
523         return;
524     }
525 }
526
527 sub auto_snmp {
528     my $node = $SNMP::MIB{$_[0]};
529 #    print STDERR "netsh::fetch_snmp $_[0] $node->{label}\n";
530     my $indexes = $node->{parent}{indexes};
531     if ($#$indexes > -1) {
532 #       print STDERR "column\n";
533         # table 
534     } else {
535         # scalar
536         if (exists($_[1])) {
537             my $sess = make_session();
538             my $val = $sess->set([$_[0],0,$_[1]]) || return;
539 #           print STDERR "scalar set: $val\n";
540             return $val;
541         } else {
542             my $sess = make_session();
543             my $val = $sess->get([$_[0],0]);
544 #           print STDERR "scalar get: $val\n";
545             return $val;
546         }
547     }
548 }
549
550 sub AUTOLOAD {
551     my $nodename = $AUTOLOAD;
552     $nodename =~ s/.*:://;
553     print STDERR "netsh::AUTOLOAD $AUTOLOAD $nodename\n";
554     if ($SNMP::MIB{$nodename}) {
555         eval << "END";
556         sub $AUTOLOAD {
557             auto_snmp($nodename, \@_);
558         }
559 END
560         goto &$AUTOLOAD;
561     }
562     print STDERR join(",",@_),"\n";
563 }
564
565 sub my_printf {
566     # drop quotes
567     my $fmt = shift;
568     $fmt = eval $fmt;
569     map { if (ref($_) eq "color_string") { $_->colorize_next(); } } @_;
570     printf($fmt, @_);
571 }
572
573 sub display_alias {
574     my $name = shift;
575     if (exists $aliases{$name}{'definition'}) {
576         if (ref($aliases{$name}{'definition'}) eq "ARRAY") {
577             print "alias $name {\n";
578             map { print "  $_\n"; } @{$aliases{$name}{'definition'}};
579             print "}\n";
580         } else {
581             print "alias $name $aliases{$name}{'definition'}\n";
582         }
583     } else {
584         print "no alias defined for \"$name\"\n";
585     }
586 }
587
588 sub make_session {
589     if (!$sess) {
590         $sess = new SNMP::Session(@sessparms);
591     }
592     return $sess;
593 }
594
595
596 sub usage {
597     print STDERR "
598 $0 [ARGUMENTS] HOSTNAME [SQL_COMMAND] 
599
600   $0 implements a simple SQL shell which maps onto SNMP.  All
601   statements issued within the shell are converted to SNMP requests and
602   sent to HOSTNAME and the results displayed in a nice table output
603   format if the Text::FormatTable module is available.  If SQL_COMMAND
604   is given on the command line, then it's interpreted and control is
605   returned to the caller.  If not, an interactive prompt is given where
606   multiple commands can be issued.
607
608 ARGUMENTS may include:
609
610   -t         delimiter separated output, don't print pretty tables.
611   -d DELIM   use DELIM as the delimiter.  A tab is the default delimiter.
612              'xml' is a special delimiter to produce xml output.
613
614 ARGUMENTS also may include the following.  See the net-snmp snmpcmd
615 manual page for details on what they mean:
616
617   -v VERSION        (default: 1)
618   -t TIMEOUT
619   -r RETRIES
620   -p PORT
621   -c COMMUNITY
622   -a AUTHPROTOCOL   (default: MD5)
623   -x PRIVPROTOCOL   (default: DES)
624   -A AUTHPASS
625   -X PRIVPASS
626   -l SECURITY_LEVEL (default: authNoPriv)
627   
628 ";
629     exit;
630 }
631
632 __END__
633
634 =head1 NAME
635
636 netsh - A shell environment for interacting with networking devices
637
638 =head1 SYNOPSIS
639
640 netsh [(subset of) snmpcmd arguments] hostname[,hostname...] [command]
641
642 =head1 OPTIONAL PERL MODULES
643
644 There are some optional perl modules which make using the shell nicer
645 in general.  These modules are:
646
647   Text::FormatTable
648   Term::ReadLine::Gnu or Term::ReadLine::Perl
649   Term::ANSIColor
650
651 You can install these by running [as root]:
652
653   perl -MCPAN -e shell
654   cpan> install Text::FormatTable
655   ...
656
657 It is B<strongly> recommend you at least install the Text::FormatTable
658 module, and if you like command line editing one of the two extra
659 Term::ReadLine modules (Gnu being the better of the two).
660
661 =head1 DESCRIPTION
662
663 The netsh script provides an interactive, console-like environment
664 suitable for monitoring and manipulating data within networking
665 devices.  The environment is highly extensible through command
666 aliases and easy-to-use SQL-like queries.
667
668 It is implemented on top of the SNMP protocol using the net-snmp
669 package and perl.  See the snmpcmd and snmp.conf manual pages for
670 details on configuring net-snmp for authentication information for
671 the networking devices you wish to access.
672
673 =head1 ABOUT THE EXAMLPES IN THIS DOCUMENT
674
675 All the examples in this document are exact cut-n-pastes from the
676 inside of the netsh shell.  This includes the "netsh> " prompt and
677 possibly other prompts as well.
678
679 =head1 COMMANDS
680
681 The following is a list of the basic core commands supported by
682 netsh.  Many default aliases are also supplied, some of which are
683 listed in the next section.  At the command prompt type "alias" for
684 a full list of all the aliases and their definitions.
685
686 =over
687
688 =item show columns from TABLE
689
690 =item select ... 
691
692 =item update ...
693
694 =item insert ...
695
696 =item delete ...
697
698 netsh supports the standard sql-like language queries of snmp tables.
699 These are implemented via the SQL::Statement parser, so any form of
700 expression it accepts netsh will accept as well.
701
702 Examples:
703
704   netsh> show columns from ifTable
705   +-----------------+
706   |           Column|
707   +-----------------+
708   |          ifIndex|
709   ...
710   netsh> select * from ifTable
711   ... [output not shown]
712   netsh> select tcpConnState, tcpConnRemotelAddress from tcpConnTable where tcpConnState = established
713   ... [output not shown]
714   netsh> update ifTable set ifAdminStatus = up where ifOperStatus = down
715   ... [output not shown]
716
717 =item SNMPOBJECT
718
719 Simple lists of objects may be given which will directly request or
720 operate on those objects.  See printf below for controlling the
721 formatting of results
722
723 Examples:
724
725   netsh> sysContact.0, sysLocation.0
726   hardaker@somewhere.net my location
727   netsh> sysContact.0 = my new contact information
728
729 =item alias my_command some other command
730
731 or
732
733 =back
734
735 alias my_many_commands {
736   command1
737   command2
738   prompt NUMBER question
739
740 }
741
742 =over
743
744 =item
745
746 You can create aliases of your frequently used commands by aliasing
747 them to an easy to remember name.  \N parameters in the defined
748 command will be replaced by the positional argument of options passed
749 to the alias name.
750
751 For multi-line defined aliases, optional prompts may be given to
752 request information from the user when the NUMBERth argument is not
753 given to the alias.  If it is not given, the prompt will be printed
754 and the user will be asked to input a value for it.  This allows the
755 easy definition of interactive commands.  The value will be inserted
756 in alias parts containing \N substitution requests.
757
758   netsh> alias interfaces select ifDescr from ifTable
759   netsh> interfaces
760   +-------+
761   |ifDescr|
762   +-------+
763   |     lo|
764   |   eth0|
765   +-------+
766   netsh> alias interface select ifDescr, ifSpeed from ifTable where ifDescr = '\1'
767   netsh> interface eth0
768   +-------+--------+
769   |ifDescr| ifSpeed|
770   +-------+--------+
771   |   eth0|10000000|
772   +-------+--------+
773
774 =item printf FORMATSTRING COMMAND
775
776 Allows B<careful> formatting of results returned by the commands.
777
778 Example:
779
780   netsh> alias interface {
781   define interface> printf "interface %s is running at %d Mb/s\n" select ifDescr, ifSpeed from ifTable where ifDescr = '\1'
782   define interface> prompt 1 Enter the interface to describe:
783   define interface> }
784   netsh> interface
785   Enter the interface to describe: eth0
786   interface eth0 is running at 10000000 Mb/s
787
788 To list the definition of an already defined command, simply exclude
789 the definition and netsh will report the definition to you:
790
791   netsh> alias interface
792   alias interface {
793     printf "interface %s is running at %d Mb/s\n" select ifDescr, ifSpeed from ifTable where ifDescr = '\1'
794     prompt 1 Enter the interface to describe:
795   }
796
797 To list all the aliases defined in the system, just type "alias" by itself.
798
799 =item watch [TIME] COMMAND
800
801 Continually watches the results of the COMMAND being run, which is run
802 every TIME seconds.  For select statements, it will attempt to mark
803 the changing values from one screen to the next by surrounding them
804 with "*"s or color (assuming you have the Term::ANSIColor perl module
805 installed) for easy picking out on the screen.
806
807 =item rehash
808
809 Re-load the alias definitions files in the common directory, as
810 well as those files found in $HOME/.snmp/netsh.
811
812 =item source FILE
813
814 loads definitons and commands from FILE into the running environment.
815
816 =back
817
818 =head1 FILES
819
820 By default, netsh will source all the definition files it can find.
821 It does this by first reading everything in
822 /usr/local/share/snmp/netsh/* and then reading everything in
823 $HOME/.snmp/netsh/*.  Everything contained in these files are
824 commands, but most frequently they entirely consist of aliases
825 definitions.
826
827 =head1 AUTHOR
828
829 bugs, comments, questions to net-snmp-users@lists.sourceforge.net
830
831 =head1 Copyright
832
833      Copyright (c) 2002 Networks Associates Technology, Inc. All
834      rights reserved.  This program is free software; you can
835      redistribute it and/or modify it under the same terms as Perl
836      itself.
837
838 =cut