How do you save and parse a command output in Expect? - tcl

I am half-way through writing an Expect script on a Linux server which is supposed to telnet to a router in order to collect some system information. So far my script can successfully make the connection, run a router command, disconnect and terminate.
The command displays a few lines which I need to parse, something I am not sure how to do in Expect. How can I save the output, grep a line, then a column from the line, and finally save the result in a file? If possible, I would like to use Expect entirely rather than a work-around (for example Expect embdded in Bash).
Thanks for your time.
jk04

Two tips for expect development:
autoexpect to lay out a framework for your automation
exp_internal 1 to show verbosely what expect is doing internally. This one is indispensable when you can't figure out why your regular expression isn't capturing what you expect.

basically, $expect_out(buffer) [1]. holds the output from last expect match to the current one. you can find your command output there.
and for the string manipulation, you can simply employ the tcl's built-in [2][3].
"How to access the result of a remote command in Expect" http://wiki.tcl.tk/2958
"regexp" http://wiki.tcl.tk/986
"string match" http://wiki.tcl.tk/4385

I've faced and solved a similar problem for interacting with bash. I believe the approach generalizes to any other interactive environment that provides no-op, fixed-string output.
Basically, I wrap the command with two fixed strings and then search for the pattern that includes these strings at the beginning and end, and save the content in between them. For example:
set var "";
expect $prompt { send "echo PSTART; $command; echo PEND;\r"; }
expect {
-re PSTART\r\n(.*)PEND\r\n$prompt { set var [ string trim $expect_out(1,string) ]; send "\r"; }
-re $prompt { set var "" ; send "\r"; }
timeout { send_user "TIMEOUT\n"; exit }
}
I suspect that this approach would work with a shell's comment characters as well, or a simple status command that returns a known output.
Then you can do whatever you need with the content of 'var'.

Related

Expect programming:How to expect a particular string

I have logged in the CUCM then we are trying to execute one command which is coming correct, but by the Expected value we are not able to execute the second command.
first we are doing like this:-
send -i $install_id "utils ctl set-cluster mixed-mode\r\n"
puts "we are hitting yes &&&&&&&&&&"
expect {
-i $install_id -re ".*" {
send -i $install_id "y\r"
puts "$$$$$$$$$$$$$$$ we are inside...."
}
}
puts "we are done %%%%%%%%%%"
return 1
here first command send -i $install_id "utils ctl set-cluster mixed-mode\r\n" is getting executed sucessfully , but it is not going inside the expect loop.
the output of first command is something like:-
admin:utils ctl set-cluster mixed-mode
This operation will set the cluster to Mixed mode. Do you want to continue? (y/n):
after this the cursor will be in next line where i have to give y and enter, the output statment inside expect is also not getting printed
I'd be doing something like:
expect {
-i $install_id
-ex "continue? (y/n):" {
puts "I FOUND A CONTINUE PROMPT"
send -i $install_id "y\r"
exp_continue
}
"$thePrompt" {
puts "I FOUND A SYSTEM PROMPT"
}
}
Like that, it would only stop that expect once the command has finished, but it will respond to the continue prompt and keep going (because exp_continue). In this case, the -ex option is suitable as that matches a literal string, a great convenience when the thing that you are looking for contains multiple regular expression metacharacters (?, ( and )).
And change "$thePrompt" for the right thing to actually match the prompt, of course.
Use autoinspect(1).
Start it, then run all your commands manually, inspect the script.exp file it created. It should have recorded the exact matched strings, in case there is some weird shell coloring going on which makes you missing your expected string but having no idea why.

How to run a TCL script to tell run in every 10 minutes?

My TCL script:
source reboot_patch.tcl
set a 1
while {$a < 10} {
exec reboot_patch.tcl
after 60000
incr a
}
I need to run "reboot_patch.tcl" script for every 1 min in my system. I wrote above script. But its running only once and its coming out.
Following is the "reboot_patch.tcl" script:
#!/usr/bin/tcl
package require Expect
spawn telnet 40.1.1.2
expect "*console."
send "\r"
expect "*ogin:"
send "test\r"
expect "*word:"
send "test\r"
expect "*>"
send "clear log\r"
expect "*#"
send "commit \r"
expect "*#"
Please suggest me a way to achieve this.
Thanks in advance.
Script to print numbers from 1 to 10 in windows 7:
#!c:\Tcl\bin\tclsh
set a 1
while { $a < 11} {
puts $a
incr a
}
I am unable to run the above script using "./" format in windows7.
In general, exec command will return the output of program execution. It is our responsibility to capture and print and manipulate it.
You have to print it manually like
puts [ exec ./reboot_patch.tcl ]
Or like,
set result [ exec ./reboot_patch.tcl ]
puts $result
Since you are using exec without printing it's result, you have not seen anything. Then how come it got executed for the first time ? Who else can do except the following ?
source reboot_patch.tcl
Well, Since you have sourced the file and it got executed which seemed to be the first time execution but which is not actually from exec command.
Note : If you are calling any of that sourced file's proc, then only it is required to source it. As far as I can see you are not having any proc there. So, source is not required at all.

Expect: extract specific string from output

I am navigating a Java-based CLI menu on a remote machine with expect inside a bash script and I am trying to extract something from the output without leaving the expect session.
Expect command in my script is:
expect -c "
spawn ssh user#host
expect \"#\"
send \"java cli menu command here\r\"
expect \"java cli prompt\"
send \"java menu command\"
"
###I want to extract a specific string from the above output###
Expect output is:
Id Name
-------------------
abcd 12 John Smith
I want to extract abcd 12 from the above output into another expect variable for further use within the expect script. So that's the 3rd line, first field by using a double-space delimiter. The awk equivalent would be: awk -F ' ' 'NR==3 {$1}'
The big issue is that the environment through which I am navigating with Expect is, as I stated above, a Java CLI based menu so I can't just use awk or anything else that would be available from a bash shell.
Getting out from the Java menu, processing the output and then getting in again is not an option as the login process lasts for 15 seconds so I need to remain inside and extract what I need from the output using expect internal commands only.
You can use regexp in expect itself directly with the use of -re flag. Thanks to Donal on pointing out the single quote and double quote issues. I have given solution using both ways.
I have created a file with the content as follows,
Id Name
-------------------
abcd 12 John Smith
This is nothing but your java program's console output. I have tested this in my system with this. i.e. I just simulated your program's output with cat. You just replace the cat code with your program commands. Simple. :)
Double Quotes :
#!/bin/bash
expect -c "
spawn ssh user#domain
expect \"password\"
send \"mypassword\r\"
expect {\\\$} { puts matched_literal_dollar_sign}
send \"cat input_file\r\"; # Replace this code with your java program commands
expect -re {-\r\n(.*?)\s\s}
set output \$expect_out(1,string)
#puts \$expect_out(1,string)
puts \"Result : \$output\"
"
Single Quotes :
#!/bin/bash
expect -c '
spawn ssh user#domain
expect "password"
send "mypasswordhere\r"
expect "\\\$" { puts matched_literal_dollar_sign}
send "cat input_file\r"; # Replace this code with your java program commands
expect -re {-\r\n(.*?)\s\s}
set output $expect_out(1,string)
#puts $expect_out(1,string)
puts "Result : $output"
'
As you can see, I have used {-\r\n(.*?)\s\s}. Here the braces prevent any variable substitutions. In your output, we have a 2nd line with full of hyphens. Then a newline. Then your 3rd line content. Let's decode the regex used.
-\r\n is to match one literal hyphen and a new line together. This will match the last hyphen in the 2nd line and the newline which in turn make it to 3rd line now. So, .*? will match the required output (i.e. abcd 12) till it encounters double space which is matched by \s\s.
You might be wondering why I need parenthesis which is used to get the sub-match patterns.
In general, expect will save the expect's whole match string in expect_out(0,string) and buffer all the matched/unmatched input to expect_out(buffer). Each sub match will be saved in subsequent numbering of string such as expect_out(1,string), expect_out(2,string) and so on.
As Donal pointed out, it is better to use single quote's approach since it looks less messy. :)
It is not required to escape the \r with the backslash in case of double quotes.
Update :
I have changed the regexp from -\r\n(\w+\s+\w+)\s\s to -\r\n(.*?)\s\s.
With this way - your requirement - such as match any number of letters and single spaces until you encounter first occurrence of double spaces in the output
Now, let's come to your question. You have mentioned that you have tried -\r\n(\w+)\s\s. But, there is a problem here with \w+. Remember \w+ will not match space character. Your output has some spaces in it till double spaces.
The use of regexp will matter based on your requirements on the input string which is going to get matched. You can customize the regular expressions based on your needs.
Update version 2 :
What is the significance of .*?. If you ask separately, I am going to repeat what you commented. In regular expressions, * is a greedy operator and ? is our life saver. Let us consider the string as
Stackoverflow is already overflowing with number of users.
Now, see the effect of the regular expression .*flow as below.
* matches any number of characters. More precisely, it matches the longest string possible while still allowing the pattern itself to match. So, due to this, .* in the pattern matched the characters Stackoverflow is already over and flow in pattern matched the text flow in the string.
Now, in order to prevent the .* to match only up to the first occurrence of the string flow, we are adding the ? to it. It will help the pattern to behave as non-greedy manner.
Now, again coming back to your question. If we have used .*\s\s, then it will match the whole line since it is trying to match as much as possible. This is common behavior of regular expressions.
Update version 3:
Have your code in the following way.
x=$(expect -c "
spawn ssh user#host
expect \"password\"
send \"password\r\"
expect {\\\$} { puts matched_literal_dollar_sign}
send \"cat input\r\"
expect -re {-\r\n(.*?)\s\s}
if {![info exists expect_out(1,string)]} {
puts \"Match did not happen :(\"
exit 1
}
set output \$expect_out(1,string)
#puts \$expect_out(1,string)
puts \"Result : \$output\"
")
y=$?
# $x now contains the output from the 'expect' command, and $y contains the
# exit status
echo $x
echo $y;
If the flow happened properly, then exit code will have value as 0. Else, it will have 1. With this way, you can check the return value in bash script.
Have a look at here to know about the info exists command.

How to store output in a variable while using expect 'send' command

Thanks.
But the account and password are needed. So I must send them and then send ovs-vsctl command.
the scripts are something like this:
spawn telnet#ip
expect -re "*login*" {
send "root"
}
expect -re "password*" {
send "****"
}
send "ovs-vsctl *******"
I want to store the output of this command send "ovs-vsctl ****", but many times I got some output of the command "send "password"", how can I get the output of send "ovs-vsctl****". The output of the command send "ovs-vsctl *** are two string and each string occupies one line.
Thanks.
Maybe:
log_user 0 ;# turn off the usual output
spawn telnet#ip
expect -re "*login*"
send "root\r"
expect -re "password*"
send "****\r"
send "ovs-vsctl *******"
expect eof
puts $expect_out(buffer) ;# print the results of the command
Expect works with an input buffer, which contains everything that is returned from the interactive application, meaning both process output and your input (as long is it is echoed from the remote device, which is usually the case).
The expect command is used to recover text from the input buffer. Each time a match is found, the buffer up to the end of that match is cleared, and saved to $expect_out(buffer). The actual match is saved to $expect_out(0,string). The buffer then resets.
What you need to do in your case is match the output with an expect statement, to get what you want.
What I would do in your case is match the remote device prompt after sending the password, then match it again after command was sent. That way the buffer after the last match will hold the needed output.
Something along the lines of:
[...]
expect -re "password*" {
send "****"
}
expect -re ">"
send "ovs-vsctl *******\r"
expect -re ">" # Better if you can use a regexp based on your knowledge of device output here - see below
puts $expect_out(buffer)
By matching using a regexp based on your knowledge of the output, you should be able to extract only the command output and not the echoed command itself. Or you can always do that after-the-fact, by using the regexp command.
Hope that helps!

exp::winnt_debug parent namespace error

I get the error "can't set "::exp::winnt_debug": parent namespace doesn't exist" when I try to run my expect script using the C implementation of expect interpreter on Windows (expect543.dll).
However the same script works fine if I run it through the ActiveState command tclsh...
The statement "set ::exp::winnt_debug 1" in the script is the cause of the error.
Any idea what might be the reason and how to resolve it?
Please find the code below
package require Expect
set ::exp::winnt_debug 1
set prompt "R4#"
set more " --More--"
expect -timeout 10 "$prompt"
set output [open result.txt "w"]
set running 1
spawn plink -telnet "144.21.12.45" -P 2004
send "enable\r"
send "\r"
send "show running-config\r"
send "\r"
while { $running > 0 } {
expect {
"\n" { puts -nonewline $output "$expect_out(buffer)" }
"$more" {send " "}
"lines *-* " { send " " }
#"$prompt" { set running 0 }
eof { set running 0 }
timeout { set running 0 }
}
}
puts "output is .."
There may be several implementations of Expect for Windows (unlike the Unix version, which has been stable for ages) and it sounds like the details of how they are implemented internally varies quite a bit between them. That's not especially surprising. Furthermore, the variable ::exp::winnt_debug is absolutely internal to a particular implementation.
The immediate fix is to change the line with the error to this:
catch {set ::exp::winnt_debug 1}
Like that, if it fails, it fails silently and won't cause the rest of the program to not run. (Enabling debugging shouldn't make any difference to whether the code runs!)
More generally, either use the ActiveState build (and work out how to package things together in the right way, bearing mind that critical dependency) or stop referring to internal features of it. It's very bad form to be poking your fingers inside the implementation of a package, as nobody ever gave a commitment to support them.