show cdp neighbor in expect script - regex

I'm pretty new to scripting in general. I'm writing an expect script that ssh'es into a Cisco switch, and runs the "show cdp neighbors" command to get a list of all the devices connected to the switch. I then save the output into a variable and exit the ssh session.
I have the username and password being set in the included file.
#!/usr/bin/expect -f
#exp_internal 1
source accountfile
set timeout 10
spawn $env(SHELL)
expect "#"
send "ssh $USERNAME#<hostname>\r"
expect {
"continue connecting" {
send_user "Adding host to ssh known hosts list...\n"
send "yes\n"
exp_continue
}
"Do you want to change the host key on disk" {
send_user "Changing host key on disk...\n"
send "yes\n"
exp_continue
}
"assword:" {
send "$PASSWORD\r"
}
}
expect "#"
send "term len 0\r"
expect "#"
send "show cdp neighbors\r"
expect "#"
set result $expect_out(buffer)
send "exit\r"
expect "#"
So then I want to take $result and look for lines that contain ' R ', and save those lines to a file (R with spaces on either side indicates a router, which is what I'm interested in)
The problem is that if the name of a connected device is long, it puts the name of the device on one line, and then the rest of the data about the device on the next line. So if I match the ' R ' string, I won't get the name of the device, since the name is on the previous line.
Device ID Local Intrfce Holdtme Capability Platform Port ID
...
<device_name_really_long>
Gig 2/0/52 171 R S I WS-C6509 Gig 3/14
<device_name2> Gig 2/0/1 131 H P M IP Phone Port 1
...
Any ideas? there's probably a regex that would do it, but I don't know squat about regex.
SOLVED: thanks to Glenn Jackman
I ended up having to add an expect condition to check if I had a full buffer, so my final code looks like this:
#!/usr/bin/expect
#exp_internal 1
match_max 10000
set expect_out(buffer) {}
set timeout 30
source accountfile
spawn $env(SHELL)
expect "#"
send "ssh $USERNAME#ol2110-3750stack.sw.network.local\r"
expect {
"continue connecting" {
send_user "Adding host to ssh known hosts list...\n"
send "yes\n"
exp_continue
}
"Do you want to change the host key on disk" {
send_user "Changing host key on disk...\n"
send "yes\n"
exp_continue
}
"assword:" {
send "$PASSWORD\r"
}
}
expect "#"
send "term len 0\r"
expect "#"
send "show cdp neighbors\r"
set result ""
expect {
{full_buffer} {
puts "====== FULL BUFFER ======"
append result $expect_out(buffer)
exp_continue
}
"#" {
append result $expect_out(buffer)
}
}
send "exit\r"
expect "#"
set devices [list]
set current_device ""
set lines [split $result "\n"]
foreach line $lines {
set line [string trim $line]
if {[llength $line] == 1} {
set current_device $line
continue
}
set line "$current_device$line\n"
if {[string match {* R *} $line]} {
lappend devices $line
}
set current_device ""
}
puts $devices

set devices [list]
set current_device ""
foreach line [split $result \n] {
if {[llength [split [string trim $line]]] == 1} {
set current_device $line
continue
}
set line "$current_device$line"
if {[string match {* R *} $line]} {
lappend devices $line
}
set current_device ""
}
# devices list should now contain "joined" routers.

Related

'if else' statement in Expect script

I am trying to create a script which will "send" input as per the output received from executing the previous script.
#!/usr/bin/expect --
set timeout 60
spawn ssh user#server1
expect "*assword*" { send "password\r"; }
expect "*$*" { send "./jboss.sh status \r"; }
if [ expect "*running*" ];
then { send "echo running \r"; }
else { send "./jboss.sh start \r"; }
fi
I am trying to do something like this, but I am stuck in the if else statement. How can I fix it?
You can simply group them into single expect statement and whichever matched, it can be processed accordingly.
#!/usr/bin/expect
set timeout 60
spawn ssh user#server1
expect "assword" { send "password\r"; }
# We escaped the `$` symbol with backslash to match literal '$'
# The last '$' sign is to represent end-of-line
set prompt "#|%|>|\\\$ $"
expect {
"(yes/no)" {send "yes\r";exp_continue}
"password:" {send "password\r";exp_continue}
-re $prompt
}
send "./jboss.sh status\r"
expect {
"running" {send "echo running\r"}
-re $prompt {send "./jboss.sh start \r"}
}
expect -re $prompt

using TCL command line, is there a way to stop showing characters?

I'm building a tool were a command with a password needs to be entered.
I want when I enter this command with the password, the command line replaces each character with "*" or " ", so the command and the password will not be observable !
is there such a command that tells the TCL interpreter "from this point, show each character entered as *", and then switch back to regular mode ?
any other suggestion will be valuable too.
In your case, you shall take "full control" over your terminal and disable its default echoing behavior (In UNIX the likes the terminal should be entered into the so-called raw mode)
Then, you can read the characters one-by-one (till max password size or till Enter is pressed) and echo '*' per each pressed character.
You got working code examples both on UNIX and Windows how doing so here
You may want reading also this link echo-free password entry TCL wiki
proc enableRaw {{channel stdin}} {
exec /bin/stty raw -echo <#$channel
}
proc disableRaw {{channel stdin}} {
exec /bin/stty -raw echo <#$channel
}
enableRaw
set c [read stdin 1]
puts -nonewline $c
disableRaw
package require twapi
proc enableRaw {{channel stdin}} {
set console_handle [twapi::GetStdHandle -10]
set oldmode [twapi::GetConsoleMode $console_handle]
set newmode [expr {$oldmode & ~6}] ;# Turn off the echo and line-editing bits
twapi::SetConsoleMode $console_handle $newmode
}
proc disableRaw {{channel stdin}} {
set console_handle [twapi::GetStdHandle -10]
set oldmode [twapi::GetConsoleMode $console_handle]
set newmode [expr {$oldmode | 6}] ;# Turn on the echo and line-editing bits
twapi::SetConsoleMode $console_handle $newmode
}
enableRaw
set c [read stdin 1]
puts -nonewline $c
disableRaw
(Assuming Linux.) By far the easiest way to handle passwords in a terminal is to turn off echoing of input but leave the terminal otherwise in cooked mode. It won't show a * for each entered character, but it does mean that you don't have to handle things like backspace (when a user realises they typed the last couple of characters wrong before hitting Return), etc.
exec /bin/stty -echo <#stdin
set password [gets stdin]
puts ""
exec /bin/stty echo <#stdin
If you've got Tcl 8.6, you can easily make this more robust with this procedure:
proc getPassword {{prompt "Password: "}} {
exec /bin/stty -echo <#stdin
try {
puts -nonewline $prompt
flush stdout
return [gets stdin]
} finally {
puts ""
flush stdout
exec /bin/stty echo <#stdin
}
}
(It's possible to use catch and some scripting to emulate try…finally but it's really annoying.)
If you have a GUI and prefer that, you make a password entry box by setting the -show option to something non-empty (e.g., * to show an asterisk).

Regular expressions in expect and shell script

Friends , im trying to automate a routing using expect , basically its a debug plugin in a special equipment that i need to log some data , to access this debug plugin my company needs to give me a responsekey based on a challengekey , its a lot of hosts and i need to deliver this by friday , what i've done so far.
#!/usr/bin/expect -f
match_max 10000
set f [open "cimc.txt"]
set hosts [split [read $f] "\n"]
close $f
foreach host $hosts {
spawn ssh ucs-local\\marcos#10.2.8.2
expect "Password: "
send "Temp1234\r"
expect "# "
send "connect cimc $host\r"
expect "# "
send "load debug plugin\r"
expect "ResponseKey#>"
sleep 2
set buffer $expect_out(buffer)
set fid [open output.txt w]
puts $fid $buffer
close $fid
sleep 10
spawn ./find-chag.sh
sleep 2
set b [open "key.txt"]
set challenge [read $b]
close $b
spawn ./find-rep.sh $challenge
sleep 3
set c [open "rep.txt"]
set response [read $c]
close $c
puts Response-IS
send "\r"
expect "ResponseKey#> "
send "$response"
}
$ cat find-chag.sh
cat output.txt | awk 'match($0,"ChallengeKey"){print substr($0,RSTART+15,38)}' > key.txt
$ cat find-rep.sh
curl bla-blabla.com/CIMC-key/generate?key=$1 | grep ResponseAuth | awk 'match($0,"</td><td>"){print substr($0,RSTART+9,35)}' > rep.txt
i dont know how to work with regexp on expect so i put the buffer output to a file and used bash script , the problem is that after i run the scripts with spawn looks like my ssh session is lost , does anyone have any tips? should i use something else instead of spawn to invoke my scripts?
expect -re "my tcl compatible regular expression goes here"
Should allow you to use regular expressions.

regexp Matching capture and exp_send

Currently I have a script which is extracting the line # number from the below menu response which has the SNMP user I’m interested in “SalVhugoV3”.
Here is the menu response in blue
Select an SNMP user:
1) cmssnmp
2) atlas
3) SalVhugoV3
Enter choice (1-3):
This working section of the script looks for the string containing “SalVhugoV3” and takes away the option #3 as that’s the option of the user it’s looking for. Then sends “3” as the snmpuserChoice for the “Enter choice” prompt.
Choose V3 User From the List/Menu, grep for $V3USER and send it's number
expect -re ".*\\(.*\\):" {
append rawOut $expect_out(buffer)
regexp { .*(([0-9])\) [puts $V3USER].*) } $expect_out(buffer) cmdOut snmpUser snmpUserChoice
exp_send -- "$snmpUserChoice\r"
}
I’m trying to do the same thing but with this extended response , as you can see the option for the user I’m looking for in this case is option “5” as it contains “SalVhugoV3” at the end of the string. I believe this is what needs to change “ regexp { .(([0-9])) [puts $V3USER].) } “ but I don’t exactly how to modify in order for it to grab the option “5” number so it can be sent via the “exp_send”.
Select an SNMP Connection to delete:
1) Connection to NMS: Host 135.9.60.143, Port 162, User cmssnmp
2) Connection to SAL: Host 135.9.158.252, Port 162, User cmssnmp
3) Connection to SAL: Host 135.9.158.236, Port 162, User cmssnmp
4) Connection to NMS: Host 135.105.6.67, Port 162, User atlas
5) Connection to SAL: Host 135.60.25.147, Port 162, User SalVhugoV3
Enter choice (1-5):
Choose V3 User From the List/Menu, grep for $V3USER and send it's number
expect -re ".*\\(.*\\):" {
append rawOut $expect_out(buffer)
regexp { .*(([0-9])\) [puts $V3USER].*) } $expect_out(buffer) cmdOut snmpUser snmpUserChoice
exp_send -- "$snmpUserChoice\r"
}
regexp "((\[0-9\])\\)\[^\\)\]*$V3USER)" $expect_out(buffer) cmdOut snmpUser snmpUserChoice
For V3USER=atlas it would give:
snmpUser='4) Connection to NMS: Host 135.105.6.67, Port 162, User atlas'
snmpUserChoice='4'
For better readability and to avoid escaping:
set gen_expr {([0-9])\)[^\)]*}
set spec_expr "($choice_expr$V3USER)"
regexp $spec_expr $expect_out(buffer) cmdOut snmpUser snmpUserChoice
BTW: term { ... [puts $V3USER] } puzzles me. There is no escaping in braces so this is just interpreted as piece of regexp thus expects one of the characters 'p', 'u', 't', 's', ' ', $', etc ...

Perl file copy duplicating output

I'm attempting to write a menu driven modular perl script that will capture user input and automate the network configuration process. This script has to be able to install required Arch packages, configure AP mode, configure either DHCP or a static address for the user selected interface and give an option to enable bridging. (EDIT: The script also needs to be able to enable and configure the dhcpd service)
The part I'm stuck on right now is creating a backup of the rc.conf file, reading the file and editing the lines that need to be modified if a network interface has already been statically configured. This script is for use in ArchLinux, I did some searching around and didn't find anything that met my needs specifically.
Using generic input for
$ip = 1.1.1.1; $Bcast = 2.2.2.2; $netmask = 3.3.3.3; $GW = 4.4.4.4;
I've spent about two hours reading about file I/O and tried several things that didn't work including scrapping the multiple file IO method and using something similar to: while(<IS>){s/^interface.?=(.*)$/"interface=#if[0] \n"/;} with inputs for each of the values that need to be replaced and couldn't get it to actually do anything.
if (system ("cat","/etc/rc.conf","|","grep","interface")){
use File::Copy "cp";
$filename = "/etc/rc.conf";
$tempfile = "/etc/rc.tmp";
$bak = "/etc/rc.bak";
cp($filename,$bak);
open(IS, $filename);
open(OS, ">$tempfile");
while(<IS>){
if($_ =~ /^interface.?=(.*)$/){ print OS"interface=#if[0] \n";}
if($_ =~ /^address.?=(.*)$/){ print OS "address=$ip\n";}
if($_ =~/^netmask.?=(.*)$/){ print OS "netmask=$netmask\n";}
if($_ =~/^broadcast.?=(.*)$/){ print OS "broadcast=$Bcast\n";}
if($_ =~/^gateway.?=(.*)$/){ print OS "gateway=$GW\n"; }
else {print OS $_;}
}
close(IS); close(OS);
unlink($filename); rename($tempfile, $filename);
}
rc.conf before
#
# /etc/rc.conf - Main Configuration for Arch Linux
. /etc/archiso/functions
LOCALE_DEFAULT="en_US.UTF-8"
DAEMON_LOCALE_DEFAULT="no"
CLOCK_DEFAULT="UTC"
TIMEZONE_DEFAULT="Canada/Pacific"
KEYMAP_DEFAULT="us"
CONSOLEFONT_DEFAULT=
CONSOLEMAP_DEFAULT=
USECOLOR_DEFAULT="yes"
LOCALE="$(kernel_cmdline locale ${LOCALE_DEFAULT})"
DAEMON_LOCALE="$(kernel_cmdline daemon_locale ${DAEMON_LOCALE_DEFAULT})"
HARDWARECLOCK="$(kernel_cmdline clock ${CLOCK_DEFAULT})"
TIMEZONE="$(kernel_cmdline timezone ${TIMEZONE_DEFAULT})"
KEYMAP="$(kernel_cmdline keymap ${KEYMAP_DEFAULT})"
CONSOLEFONT="$(kernel_cmdline consolefont ${CONSOLEFONT_DEFAULT})"
CONSOLEMAP="$(kernel_cmdline consolemap ${CONSOLEMAP_DEFAULT})"
USECOLOR="$(kernel_cmdline usecolor ${USECOLOR_DEFAULT})"
MODULES=()
UDEV_TIMEOUT=30
USEDMRAID="no"
USEBTRFS="no"
USELVM="no"
HOSTNAME="archiso"
DAEMONS=(hwclock syslog-ng)
interface=eth0
address=192.168.0.99
netmask=255.255.255.0
broadcast=192.168.0.255
gateway=192.168.0.1
rc.conf after
#
# /etc/rc.conf - Main Configuration for Arch Linux
. /etc/archiso/functions
LOCALE_DEFAULT="en_US.UTF-8"
DAEMON_LOCALE_DEFAULT="no"
CLOCK_DEFAULT="UTC"
TIMEZONE_DEFAULT="Canada/Pacific"
KEYMAP_DEFAULT="us"
CONSOLEFONT_DEFAULT=
CONSOLEMAP_DEFAULT=
USECOLOR_DEFAULT="yes"
LOCALE="$(kernel_cmdline locale ${LOCALE_DEFAULT})"
DAEMON_LOCALE="$(kernel_cmdline daemon_locale ${DAEMON_LOCALE_DEFAULT})"
HARDWARECLOCK="$(kernel_cmdline clock ${CLOCK_DEFAULT})"
TIMEZONE="$(kernel_cmdline timezone ${TIMEZONE_DEFAULT})"
KEYMAP="$(kernel_cmdline keymap ${KEYMAP_DEFAULT})"
CONSOLEFONT="$(kernel_cmdline consolefont ${CONSOLEFONT_DEFAULT})"
CONSOLEMAP="$(kernel_cmdline consolemap ${CONSOLEMAP_DEFAULT})"
USECOLOR="$(kernel_cmdline usecolor ${USECOLOR_DEFAULT})"
MODULES=()
UDEV_TIMEOUT=30
USEDMRAID="no"
USEBTRFS="no"
USELVM="no"
HOSTNAME="archiso"
DAEMONS=(hwclock syslog-ng)
interface=eth0
interface=eth0
address=1.1.1.1
address=192.168.0.99
netmask=3.3.3.3
netmask=255.255.255.0
broadcast=2.2.2.2
broadcast=192.168.0.255
gateway=4.4.4.4
I am not going to comment on the wisdom of the rest of your script, but you have:
if (system ("cat","/etc/rc.conf","|","grep","interface")){
system returns 0 on success.
So, you'll enter the block only if that system call fails.
If fact, I am on a Windows system right now with no /etc/rc.conf (but cat and grep thanks to Cygwin. Running the following script:
#!/usr/bin/env perl
use strict; use warnings;
if (system ("cat","/etc/rc.conf","|","grep","interface")){
print "*** it worked! ***\n";
if ($? == -1) {
print "failed to execute: $!\n";
}
elsif ($? & 127) {
printf "child died with signal %d, %s coredump\n",
($? & 127), ($? & 128) ? 'with' : 'without';
}
else {
printf "child exited with value %d\n", $? >> 8;
}
}
produces the output:
cat: /etc/rc.conf: No such file or directory
cat: |: No such file or directory
cat: grep: No such file or directory
cat: interface: No such file or directory
*** it worked! ***
child exited with value 1
That means system returned a failure code. Now, if you want to use shell piping and redirection, you should pass system a string, not a list, and check like this:
if (system ('cat /etc/rc.conf | grep interface') == 0) {
On the other hand, I would rather not trust shells propagating exit status.
The following should point you in a better direction:
#!/usr/bin/env perl
use strict;use warnings;
my %lookup = (
eth0 => {
address => '1.1.1.1',
broadcast => '2.2.2.2',
netmask => '3.3.3.3',
gateway => '4.4.4.4',
},
wlan0 => {
address => '5.5.5.5',
broadcast => '6.6.6.6',
netmask => '7.7.7.7',
gateway => '8.8.8.8',
},
);
while (my $line = <DATA>) {
if (my ($interface) = ($line =~ /^interface=(\S+)/)) {
print $line;
if (exists $lookup{$interface}) {
$line = process_interface(\*DATA, $lookup{$interface});
redo;
}
}
else {
print $line;
}
}
sub process_interface {
my ($fh, $lookup) = #_;
my $keys = join '|', sort keys %$lookup;
while (my $line = <DATA>) {
$line =~ s/\A($keys)=.+/$1=$lookup->{$1}/
or return $line;
print $line;
}
return;
}
__DATA__
#
# /etc/rc.conf - Main Configuration for Arch Linux
. /etc/archiso/functions
# stuff
interface=eth0
address=192.168.0.99
netmask=255.255.255.0
broadcast=192.168.0.255
gateway=192.168.0.1
interface=wlan0
address=192.168.0.99
netmask=255.255.255.0
broadcast=192.168.0.255
gateway=192.168.0.1
Output:
#
# /etc/rc.conf - Main Configuration for Arch Linux
. /etc/archiso/functions
# stuff
interface=eth0
address=1.1.1.1
netmask=3.3.3.3
broadcast=2.2.2.2
gateway=4.4.4.4
interface=wlan0
address=5.5.5.5
netmask=7.7.7.7
broadcast=6.6.6.6
gateway=8.8.8.8
The problem is your if/if/if/if/if/else chain, which should be an if/elsif/elsif/elsif/elsif/else chain. The else { print OS $_ } triggers on every line that doesn't match gateway=, including the ones that match interface, address, etc.