How can I search through an Expect variable - tcl

I'm working on an expect script that connects to a switch and then shows the configuration for an interface. I then analyze this output to check for certain things. I would like to store the output of one of the things that I'm checking, which I'm trying to accomplish by searching through $expect_out(buffer), although I'm having a difficult time finding out how to do this.
How should I go about doing this?
The script looks like the following (cut out unnecessary stuff):
send "show running-config interface $intf\r"
log_user 0
expect "#"
if {[string match "*service-policy input Access-Port*" $expect_out(buffer)]} {
set servicepolicy "yes"
} else {
set servicepolicy "no"
}
if {[string match "*mls qos trust dscp*" $expect_out(buffer)]} {
set mlsqos "yes"
} else {
set mlsqos "no"
}
if {[string matc "*Description*" $expect_out(buffer)]} {
EXTRACT DESCRIPTION STRING FROM $expect_out(buffer)
}
This is what the output of $expect_out(buffer) would typically look like:
Current configuration : 559 bytes
!
interface GigabitEthernet1/0/17
description blablabla
switchport mode access
switchport voice vlan xxxxx
no logging event link-status
authentication event fail retry 0 action authorize vlan xxxxx
authentication event no-response action authorize vlan xxxxx
authentication host-mode multi-domain
authentication port-control auto
authentication violation restrict
mab
no snmp trap link-status
dot1x pae authenticator
dot1x timeout tx-period 5
dot1x timeout supp-timeout 10
no mdix auto
spanning-tree portfast
service-policy input Access-Port
end
The "EXTRACT DESCRIPTION STRING FROM $expect_out(buffer)" line is the part that I am trying to figure out. I know how to split the line up to grab just the description, but i just do not know how to extract the line itself from the buffer variable.

Use the regexp command with the -line option:
% regexp -line {^\s*description (.*)$} $expect(buffer) -> desc
1
% puts $desc
blablabla
I assume the description is not multi-line.
Also, if you just need a boolean value,
set servicepolicy [string match "*service-policy input Access-Port*" $expect_out(buffer)]
or, do this
set servicepolicy [expr {[string match "*service-policy input Access-Port*" $expect_out(buffer)] ? "yes" : "no"}]

Related

Expect script file name too long when writing to a file

I'm new to coding and this is my first script. It works up to the point where it has to open(create) the file to write in. It then fails giving me an error somepassedNickvariable.txt file name too long. I run it as the user "qbot".
Sample output for the first expect:
2016-08-05T23:32:42 73600,565 INF Chat: 'Quadro': !ustawdomek
Sample output for the second one:
1. id=51890, Pepesza, pos=(473,1, 42,1, 1223,7), rot=(-66,1, 104,1, 0,0), remote=True, health=97, deaths=6, zombies=138, players=1, score=109, level=35, steamid=xxx, ip=xx.xx.xx.xx, ping=111
2. id=1141, Quadro, pos=(465,6, 87,1, -624,1), rot=(-30,9, 1620,0, 0,0), remote=True, health=187, deaths=1, zombies=525, players=0, score=520, level=84, steamid=xxx, ip=xx.xx.xx.xx, ping=24
Total of 6 in the game
Any help would be greatly appreciated as I can't get it to work by myself.
#!/usr/bin/expect
set timeout -1
spawn telnet localhost 8081
expect "Please enter password" {sleep 3; send "blahblah\r" }
while {1} {
expect \
{
"*INF Chat: '*': !ustawdomek" {
regexp {'(.+)':\s\!ustawdomek} $expect_out(buffer) match Nick;
set listplayers "*Total of * in the game";
send "lp\r"
expect $listplayers {
regexp "$Nick\,\\spos\=\\(\(\(\[-\]\?\\d+\)\,\\d\,\\s\(\[-\]\?\\d+\)\,\\d\,\\s\(\[-\]\?\\d+\)\,\\d\)\\)" $expect_out(buffer) match lok lok1 lok2 lok3
set file [open "/home/qbot/domki/$Nick.txt" w]
puts $file "$lok1 $lok2 $lok3"
close $file
send "pm $Nick \"\[QBOT\]Blahblah\"\r" }
}
timeout {break}
eof {break}
}
}
I don't entirely understand this script's syntax. But I guess it has something to do with the regexp {'(.+)':\s\!ustawdomek} $expect_out(buffer) match Nick;
.+ as such is a greedy regular expression and should be avoided as long as possible. I think it's trying to match a very large filename. Try to use a simpler regexp that allows only word matches and possibly limit the size of filename. For instance, \w{1,25} or something like that.

In an expect script, how do I remove a set of special characters from a string variable?

Say I have a variable that is set to some user input. I have no control over what the user will enter.
How would I go about removing all characters that are not in [A-Za-z0-9], spaces, periods, or commas?
proc getUserInput {} {
set timeout 60
send_user "\nEnter user input: "
expect_user {
-re "(.*)\n" {
set userInput $expect_out(1,string)
}
timeout {
exitTimeout "Timed out waiting for user input!"
}
}
return $userInput
}
set rawValue [ getUserInput ]
// massage variable goes here?
set massagedValue "$rawValue"
Not sure if it matters, but I'm using expect 5.45.
$ expect -v
expect version 5.45
Expect is a Tcl extension so you can use all Tcl commands when writing Expect scripts. You can try this in tclsh:
% set v1 "###the string###"
###the string###
% set v2 [regsub -all {[^ .,[:alnum:]]} $v1 ""]
the string
%

linux - telnet - script with expect

I was writing an expect-script which communicate with a server via telnet, but right now i need to evaluate the reply from server.
Usage:
./edit.expect
EXPECT script:
#!/usr/bin/expect<br>
spawn telnet ip port
expect "HUH?"
send "login testuser pass\r"
expect "good"
send "select 1\r"
expect "good"
send "me\r"
expect "nick=testuser id=ID group=testgroup login=testuser"
send "edit id=ID group=3\r"
expect "good"
send "quit\r"
If i send the command "me" i get a reply from the server which i need to evaluate.
The reply from server looks like this example... "nick=NICK id=ID group=GROUP login=LOGIN".
How do i extract the id of the reply and use it in a send-command?
I hope you could help me with that. Thanks a lot!
You can try this way too.
set user_id {}
expect -re {nick=(.*)\s+id=(.*)\s+group=(.*)\s+login=(.*)\n} {
#Each submatch will be saved in the the expect_out buffer with the index of 'n,string' for the 'n'th submatch string
puts "You have entered : $expect_out(0,string)"; #expect_out(0,string) will have the whole expect match string including the newline
puts "Nick : $expect_out(1,string)"
puts "ID : $expect_out(2,string)"
puts "Group : $expect_out(3,string)"
puts "Login : $expect_out(4,string)"
set user_id $expect_out(2,string)
}
send "This is $user_id, reporting Sir! ;)"
#Your further 'expect' statements goes below.
You can customize the regexp as per your wish and note the use of braces {} with -re flag in the expect command.
If you are using braces, Tcl won't do any variable substitution and if you need to use variable in the expect then you should use double quotes and correspondingly you need to escape the backslashes and wildcard operators.
expect lets you match the incoming strings with regular expressions and get the submatches in the expect_out() array. In your example, you could use
send "me\r"
expect -re {nick=([^ ]*) id=([^ ]*) group=([^ ]*) login=([^ ]*)}
set nick $expect_out(1,string)
set id $expect_out(2,string)
set group $expect_out(3,string)
set login $expect_out(4,string)
puts "GOT nick: $nick id: $id group: $group login: $login"
send "edit id=$id group=3\r"
etc...
EDIT: string must be in {} to avoid command expansion

How to search for multiple patterns stored in a list until all items are found or a set amount of time has passed

I'm making a simple expect script that will monitor the output of tcpdump for a list of multicast addresses. I want to know if packets are received or not from each multicast address in the list before expect times out.
I have a working solution, but it is inefficient and I believe I'm not utilizing the full power of expect and tcl. Anyway here is my current script:
set multicast_list {225.0.0.1 225.0.0.2 225.0.0.3}
send "tcpdump -i ixp1\r"
# If tcpdump does not start, unzip it and run it again
expect {
"tcpdump: listening on ixp1" {}
"sh: tcpdump: not found" {
send "gunzip /usr/sbin/tcpdump.gz\r"
expect "# "
send "tcpdump -i ixp1\r"
exp_continue
}
}
# Set timeout to the number of seconds expect will check for ip addresses
set timeout 30
set found [list]
set not_found [list]
foreach ip $multicast_list {
expect {
"> $ip" { lappend found "$ip" }
timeout { lappend not_found "$ip" }
}
}
set timeout 5
# Send ^c to stop tcpdump
send -- "\003"
expect "# "
So as you can see the script will look for each ip address one at a time and if the ip is seen it will add it to the list of found addresses. If expect times out it will add the address to the not_found list and search for the next address.
Now back to my question: Is there a way in which I can monitor tcpdump for all IP addresses simultaneously over a given amount of time. If the address were to be found I want to add it to the list of found addresses and ideally stop expecting it (this may not be possible, I'm not sure). The key is I need the script to monitor for all IP's in the list in parallel. I can't hard code each address because they will be different each time and the amount of addresses I am looking for will also vary. I could really use some help from an expect guru lol.
Thank You!
That's an interesting problem. The easiest way is probably to do runtime generation of the core of the expect script. Fortunately, Tcl's very good at that sort of thing. (Note: I'm assuming that IP addresses are all IPv4 addresses and consist of just numbers and periods; if it was a general string being inserted, I'd have to be a little more careful.)
set timeout 30
set found [list]
set not_found [list]
# Generate the timeout clause as a normal literal
set expbody {
timeout {
set not_found [array names waiting]
unset waiting
}
}
foreach ip $multicast_list {
set waiting($ip) "dummy"
# Generate the per-ip clause as a multi-line string; beware a few backslashes
append expbody "\"> $ip\" {
lappend found $ip
unset waiting($ip)
if {\[array size waiting\]} exp_continue
}\n"
}
# Feed into expect; it's none-the-wiser that it was runtime-generated
expect $expbody
set timeout 5
# Send ^c to stop tcpdump
send -- "\003"
expect "# "
You might want to puts $expbody the first few times, just so you can be sure that it is doing the right thing.
Here is my finished script. It uses the same code from Donal's solution, but I added a few checks to fix some issues that weren't accounted for.
set multicast_list {225.0.0.1 225.0.0.2 225.0.0.3}
set tcpdump_timeout 10
spawn /bin/bash
expect "] "
# Create the runtime-generated expbody to use later
# Generate the timeout clause as a normal literal
set expbody {
timeout {
set not_found [array names waiting]
unset waiting
}
}
foreach ip $multicast_list {
set waiting($ip) "dummy"
# Generate the per-ip clause as a multi-line string; beware a few backslashes
append expbody "\"> $ip\" {
set currentTime \[clock seconds\]
if { \$currentTime < \$endTime } {
if { \[ info exists waiting($ip) \] } {
lappend found $ip
unset waiting($ip)
}
if {\[array size waiting\]} exp_continue
}
}\n"
}
# Set expect timeout and create empty lists for tcpdump results
set timeout $tcpdump_timeout
set found [list]
set not_found [list]
# Start tcpdump
send "tcpdump -i ixp1\r"
expect "tcpdump: listening on ixp1"
# Get the time to stop tcpdump
set endTime [ expr [clock seconds] + $tcpdump_timeout ]
# Feed expbody into expect; it's none-the-wiser that it was runtime-generated
expect $expbody
set not_found [array names waiting]
unset waiting
# Send ^c to stop tcpdump
send -- "\003"
expect "# "

Expect : error can't read "ip": no such variable

I am a newbie in expect / TCL and trying to parse an HTML page that has output some thing like below:
<li><p>Timestamp: Wed, 14 Nov 2012 16:37:50 -0800
<li><p>Your IP address: 202.76.243.10</p></li>
<li><p class="XXX_no_wrap_overflow_hidden">Requested URL: /</p></li>
<li><p>Error reference number: 1003</p></li>
<li><p>Server ID: FL_23F7</p></li>
<li><p>Process ID: PID_1352939870.809-1-428432242</p></li>
<li><p>User-Agent: </p></li>
My script is below. I am able to get the web page which I am not able to parse the line "Your IP address:" which is giving me errors:
#!/usr/bin/expect -f
set timeout -1
spawn telnet www.whatismyip.com 80
send "GET /\r\n"
expect
set output $expect_out(buffer)
foreach line [split $output \n] {
regexp {.*<li><p>Your IP Address Is:.*?(\d+\.\d+\.\d+\.\d+)} $line ip
if {[string length ${ip}]} {
puts $ip
}
}
The error is:
Connection closed by foreign host.
can't read "ip": no such variable
while executing
"string length ${ip}"
("foreach" body line 3)
invoked from within
"foreach line [split $output \n] {
regexp {.*<li><p>Your IP Address Is:.*?(\d+\.\d+\.\d+\.\d+)} $line ip
if {[string length ${ip}]} {
..."
(file "./t4" line 7)
Any pointers where I am doing wrong?
The regular expression did not match, so the variable was not assigned. You should check the result of regexp to see if the match succeeded; when not using the -all option to regexp, you can treat it like a boolean. Try this:
foreach line [split $output \n] {
if {[regexp {<li><p>Your IP Address Is:.*?(\d+\.\d+\.\d+\.\d+)(?!\d)} $line -> ip]} {
puts $ip
}
}
The -> is really a (weird!) variable name which will hold the whole matched string; we're not interested in it (just the parenthetical part) so we use the non-alphabetic to mnemonically say “this is going to there” (the submatch to the ip variable).
Your line contains "address" (lowercase) but you're trying to match "Address" (uppercase). Add the
-nocase option to the regexp command. Also, Tcl regular expressions cannot have mixed greediness -- the first quantifier determines if the whole expression is greedy or non-greedy (I can't find where this is documented right now).
regexp -nocase {IP Address.*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})} $line -> ip
If your ultimate goal is to get your host's external IP, then go with an API solution, such as one from exip.org:
#!/usr/bin/env tclsh
set api http://api-nyc01.exip.org/?call=ip
if {[catch {exec curl --silent $api} output]} {
puts "Failed to acquire external IP"
} else {
puts "My external IP is $output"
}
Please visit their API site for more information, especially if you live outside the USA. This solution requires curl, which you might need to install.