12 $opts{'t'} = ! eval { require Text::FormatTable; };
13 $ansicolor = eval { require Term::ANSIColor; };
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.
27 require Term::ANSIColor;
50 my $str = $_[1] || $_[0][1];
51 return $_[0][0] . $str . $_[0][2];
55 my $this = [Term::ANSIColor::color($_[2]), $_[1],
56 Term::ANSIColor::color('reset')];
57 return bless($this, $_[0]);
62 if ($opts{'t'} == 0 && $colorize) {
64 package Text::FormatTable;
68 my ($width, $text) = @_;
69 my $lines = _wrap($width, $text);
71 if (ref($text) eq "color_string") {
72 $_ .= $text->color_it($_) . ' 'x($width-length($_));
74 $_ .= ' 'x($width-length($_));
82 my ($width, $text) = @_;
83 my $lines = _wrap($width, $text);
85 if (ref($text) eq "color_string") {
86 $_ = ' 'x($width-length($_)) . $text->color_it($_);
88 $_ = ' 'x($width-length($_)) . $_;
107 $opts{'l'} = 'authNoPriv';
110 getopts('hd:tR:u:l:v:a:A:x:X:p:c:t:r:',\%opts);
112 usage() if ($#ARGV == -1 || $opts{'h'});
128 foreach my $x (keys(%opts)) {
130 $params .= ";ad_SNMP_$parammap{$x}=$x";
132 push @sessparams,$parammap{$x},$x;
135 my $host = shift @ARGV;
136 $params .= ";ad_SNMP_DestHost=" . $host;
137 push @sessparms,'DestHost', $host;
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'});
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';
155 # command line command
156 netsh(join(" ",@ARGV));
159 while($cmd = $term->readline($prompt)) {
160 last if ($cmd eq "exit" || $cmd eq "quit" || $cmd eq "q");
165 # load all configuration files we can find.
167 if (-f "$ENV{'HOME'}/.snmp/netshrc") {
168 source_file("$ENV{'HOME'}/.snmp/netshrc");
170 if (-d "$ENV{'HOME'}/.snmp/netsh") {
171 foreach my $i (glob("$ENV{'HOME'}/.snmp/netsh/*")) {
172 if (-f "$i" && "$i" !~ /.*(~|.bak)$/) {
179 # command definition for sourcing a particular file
181 my $fh = new IO::File;
182 if ($fh->open("<$_[0]")) {
184 if (s/<<\s*(\w+)$//) {
196 print STDERR "no such file: $_[0]\n";
200 # export data into an external file
204 my $cmd = "create table exporttable (" . join (" varchar(255), ",@{$sth->{NAME}}) . " varchar(255))";
206 $cmd = "insert into exporttable values(" . ('?, ' x ($#_)) . "?)";
207 $insh = $exporth->prepare($cmd);
212 # the main processing function.
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*(.*)$/);
223 # print "doing [$multi_alias]: $stmt\n";
224 if ($name eq "alias" || $multi_alias) {
226 if ($stmt =~ /^prompt\s+(\d+)\s+[\"\'](.+)[\"\']/) {
227 $aliases{$current_alias}{'prompts'}[$1] = $2;
229 } elsif ($stmt =~ /^prompt\s+(\d+)\s+(.+)/) {
233 $aliases{$current_alias}{'prompts'}[$spot] = "$x ";
235 } elsif ($stmt =~ /^\s*\}\s*$/) {
240 push @{$aliases{$current_alias}{'definition'}},$stmt;
243 $stmt =~ s/^alias\s+//;
245 foreach $i (sort keys(%aliases)) {
250 ($name, $args) = ($stmt =~ /^(\w+)\s*(.*)$/);
252 display_alias($name);
255 # print "alias: $name $args\n";
258 $prompt = "define $name> ";
259 $current_alias = $name;
261 $aliases{$name}{'definition'} = [];
264 $aliases{$name}{'definition'} = $args;
269 if ($name eq "eval") {
275 if ($name eq "evalvars") {
276 # print "args1:",$args,"\n";
277 $args =~ s/\$(\w+)/$$1/eg;
278 # print "args2:",$args,"\n";
284 while (exists $aliases{$name}) {
285 # print "modified: $stmt -> ";
286 my @ARGS = split(/\s+/,$args);
288 if (ref($aliases{$name}{'definition'}) eq "ARRAY") {
289 $statements = $aliases{$name}{'definition'};
291 # maybe prompt for values
292 if ($#{$aliases{$name}{'prompts'}} > -1) {
294 for($i = 1; $i <= $#{$aliases{$name}{'prompts'}}; $i++) {
295 if (!$ARGS[$i-1] && $term) {
297 $term->readline($aliases{$name}{'prompts'}[$i]);
302 $statements = [$aliases{$name}{'definition'}];
304 foreach my $stmt (@$statements) {
306 $stmt =~ s/\\(\d+)/$ARGS[$1-1]/g;
307 # print "running $stmt\n";
308 ($name, $args) = ($stmt =~ /^(\w+)\s*(.*)$/);
314 if ($stmt =~ /^rehash$/) {
321 if ($stmt =~ /^eval (.*)/) {
325 if ($stmt =~ s/^printf\s+(\".*\")\s*(.*)/$2/) {
330 $subfn = \&my_printf;
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";
344 map { print $_->{label},"\n"; } sort { $a->{subID} <=> $b->{subID}} @{$entrynode->{children}};
346 $table = Text::FormatTable->new('|r|');
348 $table->head('Column');
350 map { $table->row($_->{label}); } sort { $a->{subID} <=> $b->{subID}} @{$entrynode->{children}};
352 print $table->render();
357 if ($stmt =~ /^source\s+(.*)/) {
362 if ($stmt =~ s/^export\s+(\S+)\s+(.*)/$2/) {
365 $exporth = DBI->connect('dbi:AnyData:');
366 $exporth->func('exporttable','CSV',$1,'ad_catalog');
367 $subfn = \&my_export;
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");
376 while(my $row = $selh->fetchrow_arrayref) {
377 push @$old_data, @$row;
380 $exporth->disconnect();
384 if ($stmt =~ /^diff\s+(.*)/) {
391 if ($stmt =~ /^watch\s+(.*)/) {
396 if ($cmd =~ s/^(\d+)\s+(.*)/$2/) {
400 $SIG{'TERM'} = sub { $running_watch = 0; };
401 $SIG{'INT'} = sub { $running_watch = 0; };
402 while($running_watch) {
407 $SIG{'TERM'} = \&exit;
408 $SIG{'INT'} = \&exit;
412 # we have an SQL statement. process it.
413 if ($stmt =~ /^(select|insert|update|delete)/) {
414 $sth = $dbh->prepare($stmt);
416 if ($stmt =~ /^select/) {
418 my $older_data = $old_data;
419 if ($running_watch == 1) {
423 while($row = $sth->fetchrow_arrayref) {
425 if ($running_watch) {
428 for($count = 0; $count <= $#$row; $count++) {
431 $older_data->[$oldcount][$count]) {
433 push @$newrow, new color_string($row->[$count],'red');
435 push @$newrow,"$begin$row->[$count]$end";
438 push @$newrow,$row->[$count];
440 if ($running_watch == 1) {
441 $old_data->[$oldcount][$count] = $row->[$count];
449 if (ref($subfn) eq "CODE") {
450 &$subfn($printfmt,@$row);
455 if ($opts{'d'} eq 'xml') {
457 for(my $xx = 0; $xx < $#{$sth->{NAME}}; $xx++) {
458 print " <$sth->{NAME}[$xx]>$row->[$xx]</$sth->{NAME}[$xx]>\n";
461 } elsif ($opts{'d'} eq 'xmlshort') {
463 for(my $xx = 0; $xx < $#{$sth->{NAME}}; $xx++) {
464 print " $sth->{NAME}[$xx]=\"$row->[$xx]\"";
468 print join($opts{'d'},@$row),"\n";
471 $table = Text::FormatTable->new('|r' x ($#$row+1) . "|");
473 $table->head(@{$sth->{NAME}});
482 print $table->render();
489 # retrieve just one variable and display it
490 if ($stmt =~ /^(get|)\s*([^\s]+)\s*[^=]/) {
491 my $sess = make_session();
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";
502 map { push @results, $_->[2] } @$resultsv;
503 if (ref($subfn) eq "CODE") {
504 &$subfn($printfmt,@results);
506 print join(" ", @results),"\n";
509 print STDERR "could not establish a SNMP session to $host\n";
515 if ($stmt =~ /^(set|)\s*([^\s]+)\s=\s(.*)$/) {
516 my $sess = make_session();
518 $sess->set([$2,undef,$1]) ||
519 print STDERR "opps: $sess->{ErrorNum} $sess->{ErrString}\n";
521 print STDERR "could not establish a SNMP session to $host\n";
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";
537 my $sess = make_session();
538 my $val = $sess->set([$_[0],0,$_[1]]) || return;
539 # print STDERR "scalar set: $val\n";
542 my $sess = make_session();
543 my $val = $sess->get([$_[0],0]);
544 # print STDERR "scalar get: $val\n";
551 my $nodename = $AUTOLOAD;
552 $nodename =~ s/.*:://;
553 print STDERR "netsh::AUTOLOAD $AUTOLOAD $nodename\n";
554 if ($SNMP::MIB{$nodename}) {
557 auto_snmp($nodename, \@_);
562 print STDERR join(",",@_),"\n";
569 map { if (ref($_) eq "color_string") { $_->colorize_next(); } } @_;
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'}};
581 print "alias $name $aliases{$name}{'definition'}\n";
584 print "no alias defined for \"$name\"\n";
590 $sess = new SNMP::Session(@sessparms);
598 $0 [ARGUMENTS] HOSTNAME [SQL_COMMAND]
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.
608 ARGUMENTS may include:
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.
614 ARGUMENTS also may include the following. See the net-snmp snmpcmd
615 manual page for details on what they mean:
617 -v VERSION (default: 1)
622 -a AUTHPROTOCOL (default: MD5)
623 -x PRIVPROTOCOL (default: DES)
626 -l SECURITY_LEVEL (default: authNoPriv)
636 netsh - A shell environment for interacting with networking devices
640 netsh [(subset of) snmpcmd arguments] hostname[,hostname...] [command]
642 =head1 OPTIONAL PERL MODULES
644 There are some optional perl modules which make using the shell nicer
645 in general. These modules are:
648 Term::ReadLine::Gnu or Term::ReadLine::Perl
651 You can install these by running [as root]:
654 cpan> install Text::FormatTable
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).
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.
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.
673 =head1 ABOUT THE EXAMLPES IN THIS DOCUMENT
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.
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.
688 =item show columns from TABLE
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.
704 netsh> show columns from ifTable
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]
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
725 netsh> sysContact.0, sysLocation.0
726 hardaker@somewhere.net my location
727 netsh> sysContact.0 = my new contact information
729 =item alias my_command some other command
735 alias my_many_commands {
738 prompt NUMBER question
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
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.
758 netsh> alias interfaces select ifDescr from ifTable
766 netsh> alias interface select ifDescr, ifSpeed from ifTable where ifDescr = '\1'
767 netsh> interface eth0
774 =item printf FORMATSTRING COMMAND
776 Allows B<careful> formatting of results returned by the commands.
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:
785 Enter the interface to describe: eth0
786 interface eth0 is running at 10000000 Mb/s
788 To list the definition of an already defined command, simply exclude
789 the definition and netsh will report the definition to you:
791 netsh> 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:
797 To list all the aliases defined in the system, just type "alias" by itself.
799 =item watch [TIME] COMMAND
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.
809 Re-load the alias definitions files in the common directory, as
810 well as those files found in $HOME/.snmp/netsh.
814 loads definitons and commands from FILE into the running environment.
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
829 bugs, comments, questions to net-snmp-users@lists.sourceforge.net
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