expect loop / expect_out(buffer) issue - for-loop

i'm new to expect. trying to write a script for cisco router that takes a list of interfaces as input, iterates over each interface, runs a command, saves output, counts number of lines in output and carries out if/else statements based on count.
I've searched on forums and pieced together the following script based on what other users have advised:
Just pasting code for the loop here for now:
foreach int $interfaces {
match_max 20000
send_user "\n"
send_user "====== Checking $int ======\n"
send_user "\n"
set capture [open output2 w]
expect "#"
send "show policy-map $int out\n"
sleep 1
expect "#"
set output $expect_out(buffer)
puts $capture $output
close $capture
set capture 0
send_user "\n\n---------- $expect_out(buffer) ------------\n\n"
set count 0
set count [exec cat output2 | wc -l]
send_user "\n=========== $int $count ===========\n"
if {$count<3} {
puts "Service policy on ! $ip ! $int ! is not working"
} else {
puts "Service policy on ! $ip ! $int is working"
}
exec echo > output2
}
However, when I run the script the file output2 and expect_out(buffer) always has just one character '#'. So i don't get the required results. For some reason, expect_out(buffer) isn't catching output of the command 'show policy-map $int out' and writing it to the file. I am guessing there's a fundamental coding mistake here in terms of the loop structure.
Help would be much appreciated!
Many thanks.

First off, read this book for expect learning: Exploring Expect
From above book:
All of the matched characters plus the characters that came earlier
but did not match are stored in a variable called expect_out(buffer).
In this case, it is nothing but your pattern which you are expecting and it is #. So what you see it right.
Now, for your requirement, please refer How to access the result of a remote command in Expect. This link has the necessary explanation for you.

Thank you again for your support. I found the issue. There was a '#' preceding the one i was interested in. Changed pattern match and now it works.

Related

howto get exit code of script running within expect

for some time I am struggling to get the exit code of a script which I am running from within expect.
It is a BASH script and the questionable part looks like this:
expect -c "
log_file $LOG
spawn su - $ora_user
expect ""
send \"source $oraenv_binary\r\"
expect \"ORACLE_SID = \[$ora_user\] \?\"
send \"$SID\r\"
expect \"The Oracle base has been set to /oracle/$SID\"
send \"$execPATHroot/$subscript $args_subscript\r\"
expect ""
send \"echo \$?\r\"
expect -re \"(\\d+)\" {
set result \$expect_out(1,string)
}
send_user \"subscript exit code: \$result\"
log_file
send \"exit\r\"
expect ""
exit [lindex \$result 3]"
sub_rc=$?
Needed to say that this is one of many tries to get the code, however, unsuccessfully. I guess that my problem lies in incorrectly escaped characters or wrong use of brackets.....
When debugging, I am getting the following:
[336] oraenv_binary=/usr/local/bin/oraenv
[338] expect -c '
log_file /var/opt/osit/oracle/log/ora_sbp_patching_root.bash.log
spawn su - oracle
expect
send "source /usr/local/bin/oraenv\r"
expect "ORACLE_SID = \[oracle\] \?"
send "H95\r"
expect "The Oracle base has been set to /oracle/H95"
send "/opt/osit/oracle/bin/ora_sbp_patching_orausr.bash -s H95 -a CHECK -p /imports/e2r2s48ifde0002/CDSAP/DB/oracle/ORA19/SBP/SBP_1915_220419_202205 -h /imports/e2r2s48ifde0002/CDSAP/DB/oracle/ORA19/SBP/SBP_1915_220419_202205/README19P_2205-70004508.HTM -u oracle\r"
expect
send "echo $?\r"
expect -re "(\d+)" {
set result $expect_out(1,string)
}
send_user "subscript exit code: $result"
log_file
send "exit\r"
expect
exit [lindex $result 3]'
.....subscript runs here OK with exit code 0 in this case
-sh-4.2$ subscript exit code: decho $?
0
-sh-4.2$ exit
logout
expected integer but got ""
while executing
"exit [lindex $result 3]"
[357] sub_rc=0
It seems to me that the regex part "(\d+)" is not OK, but perhaps, it is completely a mess... :-)
Please help.
I have read and tried these recommendations:
Is there a way to expect output without removing it from the buffer in Tcl expect?
https://wiki.tcl-lang.org/page/How+Expect+can+capture+the+exit+code+from+a+remote+command
https://www.unix.com/shell-programming-and-scripting/144812-expect-script-obtain-exit-code-remote-command.html
You have this
expect -re "(\d+)" {
set result $expect_out(1,string)
}
send_user "subscript exit code: $result"
and we can see the output is
-sh-4.2$ subscript exit code: decho $?
0
Because the regular expression "(\d+)" is in double quotes, backslash substitutions will occur, and the pattern becomes (d+) which may not match (do you get a 10 second delay at that point?) -- I suspect this is why $result is empty.
Backslashes are prevalent in regular expressions. Using braces to quote them is the way to go:
expect -re {\d+} {set result $expect_out(0,string)}
Running your expect code with expect -d (or set exp_internal 1 in the code) emits very verbose expect debug output that is extremely useful to see how your patterns are matching (or not).
Using quoted shell heredocs is (IMO) preferable to using quoted strings to encapsulate code.
Consider
expect -c "
send_user \"my home is \$env(HOME)\\n\"
"
versus
expect << 'END_CODE'
send_user "my home is $env(HOME)\n"
END_CODE
With this technique, you pass shell variable to expect through the environment:
export ora_user=oracle
expect << 'END_EXPECT'
#...
spawn su - $env(ora_user)
END_EXPECT
thanks a lot for your answer Glenn, interesting points mentioned.
Regarding the braces versus double quotes - I have changed it like that, but no effect. I have those double quotes escaped by backslash within my code - I think the effect is the same, however, definitely it looks nicer to me and evidently it is safer.
I have played with the debug mode of expect - thanks for that, I can see much more info.
I have noticed that expect holds much more stuff than I "expected" :-)
==> this is just a snippet:
.\r\n-sh-4.2$ " (spawn_id exp7) match regular expression "\d+"? (No Gate, RE only) gate=yes re=yes
expect: set expect_out(0,string) "4"
expect: set expect_out(spawn_id) "exp7"
expect: set expect_out(buffer) "\r\n-sh-4"
can't read "expect_out(1,string)": no such element in array
while executing
"set result $expect_out(1,string)"
invoked from within
"expect -re {\d+} {set result $expect_out(1,string)}"
As you can see, when I am sending the subscript to be executed I am expecting "" i.e. nothing, just the new prompt line.
However, at that point expect is full of stuff, not at all blank - I think, I need to define the prompt exactly:
-sh-4.2$
and then I need to expect it, together with echoed exit code $? and somehow separate exit code integer to get what I want...... I will keep trying.
The final code which works is as follows (much of the credit goes to Glenn for his numerous advices) :
expect -c "
log_file $LOG
spawn su - $ora_user
expect -re {\$ $}
send \"PS1='>'\r\"
expect -re {>$}
send \"source $oraenv_binary\r\"
expect {ORACLE_SID = \[$ora_user\] ? }
send \"$SID\r\"
expect \"The Oracle base has been set to /oracle/$SID\"
send \"$execPATHroot/$subscript $args_subscript\r\"
expect { (subscript) ; exp_continue }
expect -re {>$}
send \"echo \$?\r\"
expect -re {(\d+)\r\n>$} {set result \$expect_out(1,string)}
send_user \"subscript exit code:\$result\n\"
log_file
send \"exit\r\"
expect \"logout\"
exit [lindex \$result 0]"
sub_rc=$?
echo sub_rc:$sub_rc
The first thing after spawn su - $ora_user is to set the prompt by send \"PS1='>'\r\" in order to make new lines with prompt less intrusive to expect.
Then after send \"$execPATHroot/$subscript $args_subscript\r\" I have used the fact, that I have written the subscript to have every line of output populated by (subscript) keyword. So while the subscript produces the output, the expect keeps going by exp_continue.
When the $subscript ends, the prompt > appears into which the expect sends echo $? to get exit code of the $subscript.
This appears on the screen as:
>echo $?
0
>
so the code should expect the integer, return and the new line with prompt - i.e. {(\d+)\r\n>$}. At that time the expect matches the output and expect_out(1,string) is correctly populated:
send: sending "echo $?\r" to { exp7 }
Gate keeper glob pattern for '(\d+)\r\n>$' is '*
>'. Activating booster.
expect: does "" (spawn_id exp7) match regular expression "(\d+)\r\n>$"? Gate "*\r\n>"? gate=no
echo $?
0
>
expect: does "echo $?\r\n0\r\n>" (spawn_id exp7) match regular expression "(\d+)\r\n>$"? Gate "*\r\n>"? gate=yes re=yes
expect: set expect_out(0,string) "0\r\n>"
expect: set expect_out(1,string) "0"
expect: set expect_out(spawn_id) "exp7"
expect: set expect_out(buffer) "echo $?\r\n0\r\n>"
Another thing to mention is \n within send_user \"subscript exit code:\$result\n\" so to have new line next..
The last change to the code in question is:
exit [lindex \$result 0]"
I have changed the index to 0 as variable result has just one item and index 0 stands for 1st item in the list.

Sending commands to "application's shell" using "bash script" [duplicate]

This question already has answers here:
Is it possible to make a bash shell script interact with another command line program?
(6 answers)
Closed 1 year ago.
I have a program JLinkExe which opens it's own prompt uponn execution. So I normally run it like this:
JLinkExe
and then type the commands at it's prompt that appears:
J-Link>
There are many applications with it's own prompt and I am interested in a general method that would enable me to send commands to any kind of application that has it's own prompt.
I already tried two methods. They both try to send commands in this order:
connect
Enter
Enter
Enter
Enter
erase
loadbin program.bin , 0x0
r
q
but both fail. Here is the first method:
{ echo 'connect';
echo '';
echo '';
echo '';
echo '';
echo 'erase';
echo 'loadbin program.bin , 0x0';
echo 'r';
echo 'q'; } | JLinkExe
And the second method (source):
JLinkExe <<EOF
connect
erase
loadbin program.bin , 0x0
r
q
EOF
I found these method on the internet but I don't understand why they fail. Especially the first one that worked in the past...
Can anyone propose any better / working / universally applicable method?
I think it might be because here-docs do not wait for output. Unfortunately for you I switched company, thus can't test my code below.
#! /bin/bash
expect <<-EOF
set timeout -1
spawn JLinkExe
expect "J-Link> " { send "connect\r" }
expect "J-Link> " { send "\r" }
expect "J-Link> " { send "\r" }
expect "J-Link> " { send "\r" }
expect "J-Link> " { send "\r" }
expect "J-Link> " { send "erase\r" }
expect "J-Link> " { send "loadbin program.bin , 0x0\r" }
expect "J-Link> " { send "r\r" }
expect "J-Link> " { send "q\r" }
expect eof
catch wait result
exit [lindex \$result 3]
EOF
exit $?
Except waits until J-Link> turns up and then sends the command through the connection.
If it doesn't work please notify me. I'll try to help you after the weekend :-)
EDIT:
A: Why did you wrap everything in expect 2>&1 <<-EOF and EOF?
You can add expect in the shebang, but I often use it as part of my Bash scripts. My knowledge of Bash is better.
B: Why a -EOF instead of EOF?
That's because <<-EOF allows leading tabs when you want to end the here-doc. You can indent it in functions for instance.
C: Why did you redirect stderr to stdout (2>&1)?
In your case I should've removed this. I took the code from one of my other answer about expect and tailored it to your needs.
D: What does catch wait result and exit [lindex \$result 3] do after we catch the eof?
Nice question, I had to look this one up a little myself:
lindex takes 4rd argument in \$result and exits the here-doc (0 is arg 1).
\$result is set by catch wait result.
Catch takes the output of wait and puts that into result.
Wait returns four integers:
First: pid of process that's being waited on.
Second: spawn ID.
Third: -1 for errors, 0 otherwise.
Forth: Exit status of the program as set by the OS.
Sources:
https://linux.die.net/man/1/expect
https://www.tcl.tk/man/tcl/TclCmd/catch.html
https://www.tcl.tk/man/tcl/TclCmd/lindex.html
Note that you have to escape the $ in the here-doc, otherwise Bash tries to process it. Hence \$result.
E: Why you exit with exit $?
Bash exits a script with the last known error code. Although you can leave it implicitly, I like to add it anyhow. It keeps the script more readable for beginners.

Code to count number of types of files on client in Unix

Hi I am new to shell scripting.
my requirement is:
There is one server & 3 clients. On each client error logs files are generated which are of 4 types.Say type 1 error , type 2 error like this type 4 error.
I want to write a script which read all the 3 clients from server & provide me number of times 4 different type of error logs are genereted on each client.
In short it should use ssh & grep command combination.
I have written the demo script but it's not providing me the number of times different type of logs occured on clients.
#error[1]='Exception: An application error occurred during an address lookup request, please contact IT'
#error[2]='SocketTimeoutException: Read timed out'
#error[3]='Exception: The search has produced too many matches to be returned'
#error[4]='Exception: No matching address found'
error_1='exception 1'
error_2='exception 2'
function get_list_of_clients()
{
NUM_OF_CLIENTS=$(wc -l ${CLIENT_IP_LIST} | awk -F " " '{ print $1 }' )
echo $NUM_OF_CLIENTS
if [ "${NUM_OF_CLIENTS}" -gt 0 ]
then
for ((row=2; row<=$NUM_OF_CLIENTS; row++))
do
CLIENTS_IP=$(sed -n ${row}p ${CLIENT_IP_LIST}| awk -F " " '{print $3 }')
echo ${CLIENTS_IP}
# get_number_of_errors
# copy_count_errors
echo ${$error_$row}
done
fi
}
function get_number_of_errors()
{
for((row_no=1; row_no<=4; row_no++))
do
{
/usr/bin/expect - <<- EndMark
spawn ssh root#${CLIENTS_IP} "grep $error[$row_no] var/error.log |wc -l" >> /tmp/${CLIENTS_IP}_error${row_no}.txt
match_max 50000
expect {
"*yes/no*" {
send -- "yes\r"
send -- "\r"
exp_continue
}
"*?assword:*" {
send -- "${CLIENT_PASSWORD}\r"
send -- "\r"
}
}
expect eof
EndMark
}
done
}
function copy_count_errors()
{
/usr/bin/expect - <<- EndMark
spawn scp root#${CLIENTS_IP}:/tmp/${CLIENTS_IP}* /tmp/
match_max 50000
expect {
"*yes/no*" {
send -- "yes\r"
send -- "\r"
exp_continue
}
"*?assword:*" {
send -- "${CLIENT_PASSWORD}\r"
send -- "\r"
}
}
expect eof
EndMark
}
get_list_of_clients
================================================================================
please help.
This is not really an answer, an attempt to help you getting your own.
The Problem
If I understand it correctly:
Your script runs on the server
You have three clients, each has log files
Your list of clients is in a file named $CLIENT_IP_LIST, where the IP is the third field and the first line is some sort of header, which you want to exclude.
Suggestions
It seems you need to ssh and scp to the clients. If possible, I suggest setting up SSH Public Key Authentication between your server and clients. This will greatly simplify your scripts.
Use the -C flag for compression with the scp and ssh commands.
You should copy the log files from the clients to the server and do processing on the server.
If you can choose a different scripting language such as Python, Tcl (You used Expect, which is really Tcl). Bash can handle your problem, but you will find other languages work better. This is my opinion, of course.
Get one piece of your puzzle to work before moving on to the next. For example, right now, your get_list_of_clients() function does not yet working.
That being said, here is a rewrite of get_list_of_clients, which I tested and it works within my assumptions about your $CLIENT_IP_LIST file:
function get_list_of_clients() {
let row=1
while read line
do
if (( row > 1 ))
then
set $line # Breaks line into pieces
CLIENT_IP="$3"
echo "$CLIENT_IP"
fi
let row=row+1
done < $CLIENT_IP_LIST
}

how to store CMD output in one array of strings in tcl script

I'm executing the below script:
#!/usr/bin/expect -f
#!usr/bin/expect
package require Expect
spawn telnet $serverName $portNum
expect "TradeAggregator>"
send "Clients\r"
expect "Client:"
send "1\r"
expect "1-Client>"
send "Pollers\r"
expect "Client Pollers"
send "2\r"
After executing these lines:
send "Pollers\r"
expect ">"
I am getting below lines in CMD output:
"Client" Pollers
1) "ICTS_ICEFIX_Worker Worker" (ICTS_ICEFIX_Worker Poller): RUNNING
2) "NYMEX UTBAPI Worker" (NYMEX UTBAPI Poller): STOPPED
So here, I want to store the above output in one variable. Then I want to read it line by line and if any lines contains NYMEX word, then I need to fetch the first number (1) in this eg.) and perform some substring method to cut the sting.
How can I get this in tcl script?
Here is a solution:
# After you send "2\r":
expect * ;# Do this to get data in expect_out
foreach line [split $expect_out(buffer) \n] {
if {[string match *NYMEX* $line]} {
set number [scan $line "%d"] ;# Do something with that number
}
}
Discussion
After you send "2\r", we need to capture the output into a variable. Fortunately, Expect provides that with the built-in expect_out variable. All we need is to issue an expect * command
The foreach loop splits the output into lines, and look for NYMEX, if found, we extract the first number and do something useful with it.

Run cat on remote computer and send output a variable using expect

I have a bash+expect script which has to connect via ssh to the remote comp (and i can't use ssh keys, need password identification in here), read the file there, find specific line with the "hostname" (like "hostname aaaa1111") and store this hostname into the variable to be used after while. How can i get the value of the "hostname" parameter? I thought that line content will be in $expect_out(buffer) variable (so i can scan it and analyze), but it's not. My script is:
#!/bin/bash
----bash part----
/usr/bin/expect << ENDOFEXPECT
spawn bash -c "ssh root#$IP"
expect "password:"
send "xxxx\r"
expect ":~#"
send "cat /etc/rc.d/rc.local |grep hostname \r"
expect ":~#"
set line $expect_out(buffer)
puts "line = $line, expect_out(buffer) = $expect_out(buffer)"
...more script...
ENDOFEXPECT
When i try to see line variable, i see only this: line = , expect_out(buffer) = (buffer) What is the right way to get the line from the file into the variable?
Or is it possible to open the file on the remote computer with expect, scan the file and get what i need to the variable?
Here http://en.wikipedia.org/wiki/Expect there is an example:
# Send the prebuilt command, and then wait for another shell prompt.
send "$my_command\r"
expect "%"
# Capture the results of the command into a variable. This can be displayed,
set results $expect_out(buffer)
seems that it doesn't work in this case?
You might just want to try and do it all from expect, as expect can control bash.
The following should do what you've described. Not sure if this is exactly what you are trying to do.
#!/bin/sh
# the next line restarts using tclsh \
exec expect "$0" "$#"
spawn bash
send "ssh root#$IP\r"
expect "password:"
send "xxxx\r"
expect ":~#"
send "cat /etc/rc.d/rc.local |grep hostname \n"
expect ":~#"
set extractedOutput $expect_out(buffer)
set list [split $extractedOutput "\n"]
foreach line $list {
set re {(?x)
.*
(*)
-S.*
}
regexp $re $line total extractedValue
if {[info exists extractedValue] && [string length $extractedValue] > 1} {
set exportValue $extractedValue
break # We've got a match!
}
send "exit\r" # disconnect from the ssh session
if {[info exists exportValue] && [string length $exportValue] > 1}{
send "export VARIABLE $exportValue\r"
} else {
send_user "No exportValue was found - exiting\n"
send "exit\r"
close
exit 1
}
# now you can do more things in bash if you like

Resources