Variables using in Expect/TCL in Bash Script (missing operand at _#_) - bash

Hej all,
I want to use expect code in a bash script. For background information here I got my solution for the expect script: Save terminal output to variable in expect/tcl
The script works fine when I use it stand alone. But I want to use it in a bash script for processing multiple files, I have problems with the variables inside the expect part.
I got these error:
set new error bounds? (0: no, 1: yes): missing operand at _#_
in expression " _#_> 0.3 || > 0.3 "
(parsing expression " > 0.3 || > 0.3 ")
...
Code:
#! /bin/bash
MLI_offs=$1
MLI_snr=$2
MLI_diff_par=$3
/usr/bin/expect <<EOF
spawn offset_fitm $MLI_offs $MLI_snr $MLI_diff_par MLI_coffs MLI_coffsets 7.0 6 1
set range 1.5
set azimuth 1.5
while {true} {
expect "enter minimum SNR threshold:"
send "7.0\r"
expect "enter the range and azimuth error thresholds:"
send "$range $azimuth\r"
expect -re {range: ([0-9.]+) azimuth: ([0-9.]+)} {
set range $expect_out(1,string)
set azimuth $expect_out(2,string)
}
expect "set new error bounds? (0: no, 1: yes):" {
if { $range > 0.3 || $azimuth > 0.3 } {
send "1\r"
} else {
send "0\r"
break
}
}
}
interact
EOF
Thanks,
Bjoern

The problem is that the variables are being substituted inside bash as well as Tcl/Expect. Since bash replaces unknown variables with the empty string, this leaves an entirely wrong script (which in turn complains because it can't figure out what's going on). It will have broken other things too (the use of expect_out) but it happens you've not hit it.
The simplest thing is to stop using bash as a wrapper as Tcl's quite adept at doing that sort of thing itself via the argv global. Thus:
#! /usr/bin/expect
set MLI_offs [lindex $argv 0]
set MLI_snr [lindex $argv 1]
set MLI_diff_par [lindex $argv 2]
# Alternatively, replace the preceding three lines with:
# lassign $argv MLI_offs MLI_snr MLI_diff_par
spawn offset_fitm $MLI_offs $MLI_snr $MLI_diff_par MLI_coffs MLI_coffsets 7.0 6 1
set range 1.5
set azimuth 1.5
while {true} {
expect "enter minimum SNR threshold:"
send "7.0\r"
expect "enter the range and azimuth error thresholds:"
send "$range $azimuth\r"
expect -re {range: ([0-9.]+) azimuth: ([0-9.]+)} {
set range $expect_out(1,string)
set azimuth $expect_out(2,string)
}
expect "set new error bounds? (0: no, 1: yes):" {
if { $range > 0.3 || $azimuth > 0.3 } {
send "1\r"
} else {
send "0\r"
break
}
}
}
interact

you can escape the special '$' with a '\' for all expect(tcl) variables, to prevent Bash interpreting it. eg. $range should be writen as \$range in your Bash script.

Related

Expect Script - Fixing weird terminal resizing behaviour

I wrote a small expect script to connect to multiple SSH Servers.
However if I interact with the terminal after initializing the connection, the terminal window behaves very odd.
For example, if I start the script in a non-fullsize terminal, and resize the window to make it larger after the Interact, it looks like that:
Now, I had this issue in my first expect script as well. But I was able to resolve it by adding the following code:
trap {
set XZ [stty rows ]
set YZ [stty columns]
stty rows $XZ columns $YZ < $spawn_out(slave,name)
} WINCH
And this worked perfectly fine! I was able to resize the terminal without a problem.
However, I added this piece of code to my new script, where there are multiple interact's in different proc's (functions). And on window resize, I get the following error:
can't read "spawn_out(slave,name)": no such variable
while executing
"stty rows $XZ columns $YZ < $spawn_out(slave,name)"
I have no idea on how to resolve this. Here is my code:
#!/usr/bin/expect -f
set SERVER "0"
set CHOICE "0"
set SERVER_1_PKEY [exec cat /home/me/p1]
set SERVER_2_PKEY [exec cat /home/me/p2]
set SERVER_1_HOST "server1.com"
set SERVER_2_HOST "server2.com"
set SERVER_1_USER "server1user"
set SERVER_2_USER "server2user"
set SERVER_1_PORT "22"
set SERVER_2_PORT "22"
trap {
set XZ [stty rows ]
set YZ [stty columns]
stty rows $XZ columns $YZ < $spawn_out(slave,name)
} WINCH
proc s1 {SERVER_1_PKEY SERVER_1_HOST SERVER_1_USER SERVER_1_PORT} {
send_user "\033c"
spawn ssh ${SERVER_1_USER}#${SERVER_1_HOST} -p ${SERVER_1_PORT}
expect "assword:"
send "${SERVER_1_PKEY}\r"
interact
}
proc s2 {} {
send_user "\033c"
spawn ssh ${SERVER_2_USER}#${SERVER_2_HOST} -p ${SERVER_2_PORT}
expect "assword:"
send "${SERVER_2_PKEY}\r"
interact
}
set arg [lindex $argv 0]
switch $arg {
"" { set CHOICE "0" }
"1" { set CHOICE "1" }
"2" { set CHOICE "2" }
}
if {$CHOICE eq "0"} {
puts -nonewline " Input \[1,2\]: "
flush stdout
gets stdin SERVER
if {$SERVER eq "1"} { s1 $SERVER_1_PKEY $SERVER_1_HOST $SERVER_1_USER $SERVER_1_PORT }
if {$SERVER eq "2"} { s2 $SERVER_2_PKEY $SERVER_2_HOST $SERVER_2_USER $SERVER_2_PORT }
}
if {$CHOICE eq "1"} { s1 $SERVER_1_PKEY $SERVER_1_HOST $SERVER_1_USER $SERVER_1_PORT }
if {$CHOICE eq "2"} { s2 $SERVER_2_PKEY $SERVER_2_HOST $SERVER_2_USER $SERVER_2_PORT }
Can anyone help me resolve this issue or tell me what I'm missing?
When you call spawn inside a procedure the array variable spawn_out(slave,name) has the scope of that procedure only. Usually, you can just make this into a global scope by declaring it as such inside each procedure:
proc s1 {...} {
global spawn_out
...
spawn ...
}
send_user $spawn_out(slave,name)

How to pass a bash array into expect

I have a shell script that passes a array variable to expect. But, in the expect part, it only takes the first argument and says "couldn't read file --server: no such file or directory"
below is the example program:-
Here i want the complete value of ${CMPREQUEST_ARGS[#]} which is
cmpclient --ir --server 10.10.10.10 --port 4040
CMPREQUEST=($CMPCLIENT "${CMPREQUEST_ARGS[#]}")
echo "TEST:${CMPREQUEST[#]}:TEST" //echo prints the value of ${CMPREQUEST[#]} correctly.
expect -c "
log_file -noappend -a \"/srv/Log/log/cmpclient-$app_id.log\"
log_user 1
set RET_VAL 1
set timeout 86400
puts "TEST2:${CMPREQUEST[#]}:TEST2"
spawn \${CMPREQUEST[#]}
expect {
-re \"SUCCESS\:\ write\ X509\" {
set RET_VAL 0
}
timeout { set RET_VAL 1 }
}
exit \$RET_VAL
"
exit $?
I am getting this error in spawn
couldn't read file --server: no such file or directory
Please guide..Any help would be highly appreciated.
Thanks in advance!
This boils down to quoting hell. Use a shell here-doc to hold the expect code, then you don't need to escape all the "interior" quotes. Your main issue is the spawn command, where you are preventing the shell from expanding the array.
Try this:
expect <<END_EXPECT
log_file -noappend -a /srv/Log/log/cmpclient-$app_id.log
log_user 1
set RET_VAL 1
set timeout 86400
puts "TEST2:${CMPREQUEST[#]}:TEST2"
spawn ${CMPREQUEST[#]}
expect {
-re {SUCCESS: write X509} {
set RET_VAL 0
}
timeout { set RET_VAL 1 }
}
exit \$RET_VAL
END_EXPECT
Only the expect variable RET_VAL needs to be protected from the shell.

Expect returns same value

I need to be able to return a different password after the first one fails which will be the second time the prompt asks for the same expect value "Password:"
(expect -c "
#exp_internal 1
set passwords {PASS1 PASS2}
set index 0
set timeout 20
# Start the session with the input variable and the rest of the hostname
spawn telnet $host
set timeout 3
expect {
-ex \"Password:\" {
send \"[lindex $passwords $index]\r\"
incr index
exp_continue;
}
}
I just can't get it to work. It looks like there is nothing in the lindex send:
-ex "Password:" {
send "[lindex ]\r"
The problem is you're using double quotes to group the expect script -- bash is interpreting the $variables as bash variables. You need to either:
use single quotes to delimit the script, or
escape all the \$expect_variables so bash does not substitute them first.

Expect script clause evaluation

In my expect script, my goal is to send a command to show the properties of the two processors on a motherboard. Please assume the remote logging in is successful. It's where the send clause variables are not evaluated successfully.
I have a procedure and a variable:
set showcpu "show -d properties /SYS/MB/P\r"
I created a while loop to execute do a "send" if the "cpu" count starts at 0 and less than 2.
set cpu 0
while { $cpu < 2 } {
expect {
-re $prompt {send "${showcpu}${cpu}\r"; }
timeout {
my_puts "ILOM prompt timeout error-2" [ list $fh1 $fh3 stdout ]
exit 1
}
}
set cpu [ expr {$cpu + 1} ]
}
The execution result is this:
[BL0/SP]-> show -d properties /SYS/MB/P
show: Invalid target /SYS/MB/P
[BL0/SP]-> 0
Invalid command '0' - type help for a list of commands.
I wanted the script to combine the value $showcpu with $cpu and it should look like this:
show -d properties /SYS/MB/P0 and show -d properties /SYS/MB/P1.
Could someone please educate me on what I need to do to accomplish that?
The variable ${showcpu} itself already contains "\r" (according to 1.).
Either define it without "\r":
set showcpu "show -d properties /SYS/MB/P"
or use string trim (http://wiki.tcl.tk/10174):
send "[string trim ${showcpu}]${cpu}\r"
I would recommend to trim the white spaces on the place where the variable is set, not at the places where the variable is used.

while loops within expect

I am using expect within bash. I want my script to telnet into a box, expect a prompt, send a command. If there is a different prompt now, it has to proceed or else it has to send that command again.
My script goes like this:
\#!bin/bash
//I am filling up IP and PORT1 here
expect -c "
set timeout -1
spawn telnet $IP $PORT1
sleep 1
send \"\r\"
send \"\r\"
set temp 1
while( $temp == 1){
expect {
Prompt1 { send \"command\" }
Prompt2 {send \"Yes\"; set done 0}
}
}
"
Output:
invalid command name "while("
while executing
"while( == 1){"
Kindly help me.
I tried to change it to while [ $temp == 1] {
I am still facing the error below:
Output:
invalid command name "=="
while executing
"== 1"
invoked from within
"while [ == 1] {
expect {
This is how I'd implement this:
expect -c '
set timeout -1
spawn telnet [lindex $argv 0] [lindex $argv 1]
send "\r"
send "\r"
expect {
Prompt1 {
send "command"
exp_continue
}
Prompt2 {
send "Yes\r"
}
}
}
' $IP $PORT1
use single quotes around the expect script to protect expect variables
pass the shell variables as arguments to the script.
use "exp_continue" to loop instead of an explicit while loop (you had the wrong terminating variable name anyway)
The syntax for while is "while test body". There must be a spce between each of those parts which is why you get the error "no such command while)"
Also, because of tcl quoting rules, 99.99% of the time the test needs to be in curly braces. So, the syntax is:
while {$temp == 1} {
For more information see http://tcl.tk/man/tcl8.5/TclCmd/while.htm
(you probably have other problems related to your choice of shell quotes; this answer addresses your specific question about the while statement)

Resources