Expect script clause evaluation - expect

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.

Related

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

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.

Detect empty command

Consider this PS1
PS1='\n${_:+$? }$ '
Here is the result of a few commands
$ [ 2 = 2 ]
0 $ [ 2 = 3 ]
1 $
1 $
The first line shows no status as expected, and the next two lines show the
correct exit code. However on line 3 only Enter was pressed, so I would like the
status to go away, like line 1. How can I do this?
Here's a funny, very simple possibility: it uses the \# escape sequence of PS1 together with parameter expansions (and the way Bash expands its prompt).
The escape sequence \# expands to the command number of the command to be executed. This is incremented each time a command has actually been executed. Try it:
$ PS1='\# $ '
2 $ echo hello
hello
3 $ # this is a comment
3 $
3 $ echo hello
hello
4 $
Now, each time a prompt is to be displayed, Bash first expands the escape sequences found in PS1, then (provided the shell option promptvars is set, which is the default), this string is expanded via parameter expansion, command substitution, arithmetic expansion, and quote removal.
The trick is then to have an array that will have the k-th field set (to the empty string) whenever the (k-1)-th command is executed. Then, using appropriate parameter expansions, we'll be able to detect when these fields are set and to display the return code of the previous command if the field isn't set. If you want to call this array __cmdnbary, just do:
PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ '
Look:
$ PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ '
0 $ [ 2 = 3 ]
1 $
$ # it seems that it works
$ echo "it works"
it works
0 $
To qualify for the shortest answer challenge:
PS1='\n${a[\#]-$? }${a[\#]=}$ '
that's 31 characters.
Don't use this, of course, as a is a too trivial name; also, \$ might be better than $.
Seems you don't like that the initial prompt is 0 $; you can very easily modify this by initializing the array __cmdnbary appropriately: you'll put this somewhere in your configuration file:
__cmdnbary=( '' '' ) # Initialize the field 1!
PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ '
Got some time to play around this weekend. Looking at my earlier answer (not-good) and other answers I think this may be probably the smallest answer.
Place these lines at the end of your ~/.bash_profile:
PS1='$_ret$ '
trapDbg() {
local c="$BASH_COMMAND"
[[ "$c" != "pc" ]] && export _cmd="$c"
}
pc() {
local r=$?
trap "" DEBUG
[[ -n "$_cmd" ]] && _ret="$r " || _ret=""
export _ret
export _cmd=
trap 'trapDbg' DEBUG
}
export PROMPT_COMMAND=pc
trap 'trapDbg' DEBUG
Then open a new terminal and note this desired behavior on BASH prompt:
$ uname
Darwin
0 $
$
$
$ date
Sun Dec 14 05:59:03 EST 2014
0 $
$
$ [ 1 = 2 ]
1 $
$
$ ls 123
ls: cannot access 123: No such file or directory
2 $
$
Explanation:
This is based on trap 'handler' DEBUG and PROMPT_COMMAND hooks.
PS1 is using a variable _ret i.e. PS1='$_ret$ '.
trap command runs only when a command is executed but PROMPT_COMMAND is run even when an empty enter is pressed.
trap command sets a variable _cmd to the actually executed command using BASH internal var BASH_COMMAND.
PROMPT_COMMAND hook sets _ret to "$? " if _cmd is non-empty otherwise sets _ret to "". Finally it resets _cmd var to empty state.
The variable HISTCMD is updated every time a new command is executed. Unfortunately, the value is masked during the execution of PROMPT_COMMAND (I suppose for reasons related to not having history messed up with things which happen in the prompt command). The workaround I came up with is kind of messy, but it seems to work in my limited testing.
# This only works if the prompt has a prefix
# which is displayed before the status code field.
# Fortunately, in this case, there is one.
# Maybe use a no-op prefix in the worst case (!)
PS1_base=$'\n'
# Functions for PROMPT_COMMAND
PS1_update_HISTCMD () {
# If HISTCONTROL contains "ignoredups" or "ignoreboth", this breaks.
# We should not change it programmatically
# (think principle of least astonishment etc)
# but we can always gripe.
case :$HISTCONTROL: in
*:ignoredups:* | *:ignoreboth:* )
echo "PS1_update_HISTCMD(): HISTCONTROL contains 'ignoredups' or 'ignoreboth'" >&2
echo "PS1_update_HISTCMD(): Warning: Please remove this setting." >&2 ;;
esac
# PS1_HISTCMD needs to contain the old value of PS1_HISTCMD2 (a copy of HISTCMD)
PS1_HISTCMD=${PS1_HISTCMD2:-$PS1_HISTCMD}
# PS1_HISTCMD2 needs to be unset for the next prompt to trigger properly
unset PS1_HISTCMD2
}
PROMPT_COMMAND=PS1_update_HISTCMD
# Finally, the actual prompt:
PS1='${PS1_base#foo${PS1_HISTCMD2:=${HISTCMD%$PS1_HISTCMD}}}${_:+${PS1_HISTCMD2:+$? }}$ '
The logic in the prompt is roughly as follows:
${PS1_base#foo...}
This displays the prefix. The stuff in #... is useful only for its side effects. We want to do some variable manipulation without having the values of the variables display, so we hide them in a string substitution. (This will display odd and possibly spectacular things if the value of PS1_base ever happens to begin with foo followed by the current command history index.)
${PS1_HISTCMD2:=...}
This assigns a value to PS1_HISTCMD2 (if it is unset, which we have made sure it is). The substitution would nominally also expand to the new value, but we have hidden it in a ${var#subst} as explained above.
${HISTCMD%$PS1_HISTCMD}
We assign either the value of HISTCMD (when a new entry in the command history is being made, i.e. we are executing a new command) or an empty string (when the command is empty) to PS1_HISTCMD2. This works by trimming off the value HISTCMD any match on PS1_HISTCMD (using the ${var%subst} suffix replacement syntax).
${_:+...}
This is from the question. It will expand to ... something if the value of $_ is set and nonempty (which it is when a command is being executed, but not e.g. if we are performing a variable assignment). The "something" should be the status code (and a space, for legibility) if PS1_HISTCMD2 is nonempty.
${PS1_HISTCMD2:+$? }
There.
'$ '
This is just the actual prompt suffix, as in the original question.
So the key parts are the variables PS1_HISTCMD which remembers the previous value of HISTCMD, and the variable PS1_HISTCMD2 which captures the value of HISTCMD so it can be accessed from within PROMPT_COMMAND, but needs to be unset in the PROMPT_COMMAND so that the ${PS1_HISTCMD2:=...} assignment will fire again the next time the prompt is displayed.
I fiddled for a bit with trying to hide the output from ${PS1_HISTCMD2:=...} but then realized that there is in fact something we want to display anyhow, so just piggyback on that. You can't have a completely empty PS1_base because the shell apparently notices, and does not even attempt to perform a substitution when there is no value; but perhaps you can come up with a dummy value (a no-op escape sequence, perhaps?) if you have nothing else you want to display. Or maybe this could be refactored to run with a suffix instead; but that is probably going to be trickier still.
In response to Anubhava's "smallest answer" challenge, here is the code without comments or error checking.
PS1_base=$'\n'
PS1_update_HISTCMD () { PS1_HISTCMD=${PS1_HISTCMD2:-$PS1_HISTCMD}; unset PS1_HISTCMD2; }
PROMPT_COMMAND=PS1_update_HISTCMD
PS1='${PS1_base#foo${PS1_HISTCMD2:=${HISTCMD%$PS1_HISTCMD}}}${_:+${PS1_HISTCMD2:+$? }}$ '
This is probably not the best way to do this, but it seems to be working
function pc {
foo=$_
fc -l > /tmp/new
if cmp -s /tmp/{new,old} || test -z "$foo"
then
PS1='\n$ '
else
PS1='\n$? $ '
fi
cp /tmp/{new,old}
}
PROMPT_COMMAND=pc
Result
$ [ 2 = 2 ]
0 $ [ 2 = 3 ]
1 $
$
I need to use great script bash-preexec.sh.
Although I don't like external dependencies, this was the only thing to help me avoid to have 1 in $? after just pressing enter without running any command.
This goes to your ~/.bashrc:
__prompt_command() {
local exit="$?"
PS1='\u#\h: \w \$ '
[ -n "$LASTCMD" -a "$exit" != "0" ] && PS1='['${red}$exit$clear"] $PS1"
}
PROMPT_COMMAND=__prompt_command
[-f ~/.bash-preexec.sh ] && . ~/.bash-preexec.sh
preexec() { LASTCMD="$1"; }
UPDATE: later I was able to find a solution without dependency on .bash-preexec.sh.

expect loop / expect_out(buffer) issue

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.

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.

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