I'm writting an expect script to automate some tests. To detect when a general error occurs (which can happen at any phase of the test), instead of repeating the same pattern (for instance, "Rebooting system") on each expect command, I decided to use expect_background for that kind of condition.
What I understand about the expect_background command is that it is checked against all input text before the currently active expect. So, if both expect_background and expect are waiting for the same pattern, I expect (with the pun, please ;-) ) both actions to be triggered. For instance:
#!/usr/bin/expect -f
spawn ./counter.sh
expect_background {
"5" { puts "expect-bg"; }
}
expect {
"9" { puts "expect-9"; }
}
expect {
"20" { puts "expect-20"; }
}
The ./counter.sh script counts from 1 to 20 with intervals of 1 second. According to the behavior I described above, "expect-bg" should be printed at 5 and 15, expect-9 at 9, and "expect-20" at 20. But here is what I get:
$ ./expect_bg.expect
spawn ./counter.sh
1
2
3
4
5
expect-bg
6
7
8
9
10
11
12
13
14
15
expect-bg
16
17
18
19
20
Why is that happening? And how to I get the behavior described?
I can produce your desired output with expect_before and exp_continue
#!/usr/bin/expect -f
spawn sh counter.sh
expect_before {
5 {puts "expect_before"; exp_continue}
}
expect {
9 {puts "exp_9"}
}
expect {
20 {puts "exp_20"}
}
$ expect counter.exp
spawn sh counter.sh
1
2
3
4
5
expect_before
6
7
8
9
exp_9
10
11
12
13
14
15
expect_before
16
17
18
19
20
exp_20
When you have
expect_before {
5 {puts "expect_before"; exp_continue}
}
expect {
9 {puts "exp_9"}
}
you effectively have this:
expect {
5 {puts "expect_before"; exp_continue}
9 {puts "exp_9"}
}
If you omit the exp_continue, that expect statement ends with the first 5, so you never get a chance to look for 9.
Related
Say I have following program:
$ cat test1.c
#include <stdio.h>
int main() {
int i;
for (i = 0; i < 10; ++i) {
int n, res;
res = scanf("%d", &n);
if (res != 1) break;
printf("%d\n", n * K);
}
}
Compilation is simple:
$ gcc -DK=2 test1.c -o t2
$ gcc -DK=3 test1.c -o t3
I am then using expect to automate process: one program sends output to antother.
$ cat connect.tcl
#!/usr/bin/env expect
spawn ./t2
set idt2 $spawn_id
spawn ./t3
set idt3 $spawn_id
send -i $idt2 "1\r"
expect {
-i $idt2 -re {(\d+)\s*(\d+)} {
set q $expect_out(2,string)
send -i $idt3 "$q\n"
expect -i $idt3 -re {(\d+)\s*(\d+)}
set a $expect_out(2,string)
send -i $idt2 "$a\n"
exp_continue
}
-i $idt2 eof {
exp_continue
}
-i $idt3 eof {
}
timeout {
puts "$expect_out(buffer)"
}
}
Run it like this:
$ expect connect.tcl
1
2
2
6
6
12
12
36
36
72
72
...... etc .....
Everything is good but I am seeing everything 2 times.
What I want is:
$ expect connect.tcl
1
2
6
12
36
72
...... etc .....
I. e. I want first program to take input from second and vice versa and print only what is need to be printed.
I tried different versions of spawn -noecho, etc, but looks like nothing works. Also please note, that I am matching pair of numbers in my expect script: of course in desired solution regular expressions will slightly differ.
Will appreciate any ideas.
By default expect logs interactions with spawned processes to stdout so I'll guess what you're seeing is one process outputting "12" and then you writing the "12" to the other. Try disabling that with log_user 0 and explicitly puts what you want to see instead.
I need to know if Bash has some solution for my case. I need after some conditions to do a "double return". I mean, to perform a return of a function and also return the parent function to skip the rest of the code of that parent function.
I know that I can do a conditional using function return values to achieve this. But I'd like to know if in Bash exist something like "break 2" for functions. I don't want if possible to modify the code of the parent function because as you can imagine, in my real script there are dozens of functions and I don't want to modify all of them.
Example:
#!/bin/bash
function sublevelone() {
echo "sublevelone"
# Return 2, or break 2 or something :D
}
function main() {
sublevelone
echo "This is the part of the code to being avoid executed"
}
main
I don't know what the bash experts will think, but this works at least for simple cases:
multireturn(){
[ -n "$1" ] && poplevel="$1"
if [ "$poplevel" -ge 0 ]; then
trap multireturn DEBUG
shopt -s extdebug
(( poplevel-- ))
return 2
else
shopt -u extdebug
trap - DEBUG
return 0
fi
}
This makes use of the DEBUG trap and the extdebug flag:
extdebug
If set at shell invocation, arrange to execute the
debugger profile before the shell starts, identical to
the --debugger option. If set after invocation, behav-
ior intended for use by debuggers is enabled:
1. The -F option to the declare builtin displays the
source file name and line number corresponding to
each function name supplied as an argument.
2. If the command run by the DEBUG trap returns a
non-zero value, the next command is skipped and
not executed.
3. If the command run by the DEBUG trap returns a
value of 2, and the shell is executing in a sub-
routine (a shell function or a shell script exe-
cuted by the . or source builtins), the shell
simulates a call to return.
4. BASH_ARGC and BASH_ARGV are updated as described
in their descriptions above.
5. Function tracing is enabled: command substitu-
tion, shell functions, and subshells invoked with
( command ) inherit the DEBUG and RETURN traps.
6. Error tracing is enabled: command substitution,
shell functions, and subshells invoked with (
command ) inherit the ERR trap.
Example usage:
#!/bin/bash
multireturn(){
[ -n "$1" ] && poplevel="$1"
if [ "$poplevel" -ge 0 ]; then
trap multireturn DEBUG
shopt -s extdebug
(( poplevel-- ))
return 2
else
shopt -u extdebug
trap - DEBUG
return 0
fi
}
# define 8 levels of function calls
# (level N prints output, calls level N+1, then prints more output)
for i in $(seq 1 8); do
eval \
'level'$i'(){
echo -n " '$i'"
level'$((i+1))'
echo -n "('$i')"
}'
done
# final level calls multireturn
level9(){
echo -n " 9"
multireturn $n
echo -n "(9)"
}
# test various skip amounts
for i in $(seq 0 10); do
echo -n "$i:"
n=$i
level1
echo .
done
echo
echo done
Result:
0: 1 2 3 4 5 6 7 8 9(9)(8)(7)(6)(5)(4)(3)(2)(1).
1: 1 2 3 4 5 6 7 8 9(8)(7)(6)(5)(4)(3)(2)(1).
2: 1 2 3 4 5 6 7 8 9(7)(6)(5)(4)(3)(2)(1).
3: 1 2 3 4 5 6 7 8 9(6)(5)(4)(3)(2)(1).
4: 1 2 3 4 5 6 7 8 9(5)(4)(3)(2)(1).
5: 1 2 3 4 5 6 7 8 9(4)(3)(2)(1).
6: 1 2 3 4 5 6 7 8 9(3)(2)(1).
7: 1 2 3 4 5 6 7 8 9(2)(1).
8: 1 2 3 4 5 6 7 8 9(1).
9: 1 2 3 4 5 6 7 8 9.
10: 1 2 3 4 5 6 7 8 9
done
This is kinda whacky but if you use parentheses to define levelone, it will execute the function in a subshell and then you can exit out of that shell from an inner function. That said, I think it's more appropriate to use return to send back value that you check for in the parent function.
#!/bin/bash
function leveltwo() {
echo "two"
exit
}
function levelone() (
echo "one"
leveltwo
echo "three"
)
levelone
echo "four"
Will print:
one
two
four
Why don't you return an exit status from the function and add an if-statement to main like you normally would in any other programming/scripting language?
#!/bin/bash
function sublevelone() {
echo "sublevelone"
[ 0 -eq 1 ]
return $? # Returns 1 in this case because 0 != 1
}
function main() {
sublevelone
[ $? -eq 0 ] || return 1 # Return in case of error
echo "This is the part of the code to being avoid executed"
}
main
exit 0
Just a short version of #jhnc answer for exactly 2-level return from a function:
trap 'trap "shopt -u extdebug; trap - DEBUG; return 0" DEBUG; return 2' DEBUG
shopt -s extdebug
return
I've got an expect script that looks a bit like this:
set timeout 15
spawn someprocess
expect "a line"
expect "another line"
expect "some other line"
Essentially, it's waiting until these lines appear. There are no actions to be taken.
I don't want to write the following for every line that I'm looking for:
expect {
"a line" {}
timeout { exit 1 }
}
I want expect to return a non-zero status code (i.e. in $?) if it times out at any point. How do I do this?
You can setup an expect_before line that is run "in parallel" with any later expect commands to test for timeout. Just add after your spawn command
expect_before timeout { exit 1 }
If you want to error out if the spawned process exits, you can combine them as follows:
expect_before {
timeout { exit 1 }
eof { exit 1 }
}
In my cron tab file, I have
* * * * * /Users/ajgauravdeep/test.sh
which looks like
1 #!/bin/sh
2
3 /bin/echo "Downloading builds"
4 #~luna/bin/mountebuild
5
6 #sleep 10
7
8 ##############---------Variables---------##############
9
10 fileWithBuildPath="/tmp/process.tmp.file.txt"
11 skihillDir="xyz"
12 lastBuild=`/bin/cat $fileWithBuildPath`
13 curBuild=`/usr/bin/readlink -n $skihillDir/x`
14
15 ##############---------Variables---------##############
16
17 /bin/echo lastbuild is $lastBuild
18
19 if [ "$curBuild" != "$lastBuild" ]; then
20 lastBuild=$curBuild
21 /bin/echo We have a new build :$curBuild
22 /bin/rm $fileWithBuildPath
23 /bin/echo "$lastBuild" > $fileWithBuildPath
24 fi
I don't see any output coming every minute on screen but when I have
* * * * * /Users/ajgauravdeep/test.sh > <some file>
I see that file is populated. Can anyone help?
Jobs run by cron are not connected to any terminal, much less your current terminal. You can't expect to a job with cron to write to a terminal.
I am using a Bash alias that allows me to shorten the SSH command in order for me to log into my routers. Quite trivial, but a time saver! What I would now like to do is take this a step further and fully automate the logging-in of the routers.
For example in my ~/.bashrc file I have the following entry:
sshFuncB()
{
ssh -o StrictHostKeyChecking=no superuser#$1 - | /usr/bin/expect<<EOF
set timeout 5
set send_human {.1 .3 1 .05 2}
expect {
"password: " { send -h "MYPASSWORD\r" }
"No route to host" { exit 1 }
timeout { exit 1 }
}
set timeout 2
sleep 1
expect {
"N]?" { send "y\r"; exp_continue }
timeout { exit 1 }
}
expect eof
EOF
}
alias z=sshFunc
However, when I type z myrouterhostname this does not give the desired output. I must find a way to start the SSH connection and have expect automate logging in before returning control to user.
Any ideas?
This can be done as follows,
sshFuncB()
{
expect -c "
spawn ssh -o StrictHostKeyChecking=no superuser#$1
set timeout 5
set send_human {.1 .3 1 .05 2}
expect {
\"password: \" { send -h \"MYPASSWORD\r\" }
\"No route to host\" { exit 1 }
timeout { exit 1 }
}
set timeout 2
sleep 1
expect {
\"N]?\" { send \"y\r\"; exp_continue }
timeout { exit 1 }
}
expect eof
"
}
alias z=sshFuncB
Note the use of -c flag in expect which you can refer from here of you have any doubts.
If we use double quotes for the expect code with -c flag, it will allow the bash substitutions. If you use single quotes for the same, then bash substitutions won't work. (You have used #1 inside expect, which is why I used double quotes) Since I have used double quotes for the whole expect code, we have to escape the each double quotes with backslash inside the expect statement like as follows,
expect {
# Escaping the double quote with backslash
\"password: \" {some_action_here}
}
One more update. Since this is about connecting to the router and do some of your manual operations, then it is better to have interact at the end.