问题
I am writing some expect commands in bash.
Script:
#!/bin/bash
set timeout -1
expect -c "
spawn telnet $IP $PORT1
sleep 1
send \"\r\"
send \"\r\"
expect Prompt1>
interact timeout 20 {
sleep 1
}
expect {
Prompt2> {send \"dir\r\" }
}
"
My intentions with the script are, first let it telnet into a machine, when it sees Prompt1, let it give control to me, I will execute a command to load a specific image. Then wait until Prompt2 shows up (which indicates image has been loaded). Then Let it execute the further set of commands.
After running the script, I could get into the interactive mode, load my image. The problem is getting out of interactive mode on the remote machine and giving back control to it.
The Error which I got:
expect: spawn id exp4 not open
while executing
"expect -nobrace Prompt2 {send "dir\r" }"
invoked from within
"expect {
Prompt2 {send "dir\r" }
}"
How can I do this?
回答1:
Your problem is two-fold...
You should interact with an explicit
return
, and give it some way to know you've released control... in this case, I use three plus signs and hit enter.After you return control, the script will need to get the prompt again, which means the first thing you do after returning control to expect is send another
\r
. I edited for what I think you're trying to do...
Example follows...
#!/bin/bash
set timeout -1
expect -c "
spawn telnet $IP $PORT1
sleep 1
send \"\r\"
send \"\r\"
expect Prompt1>
interact +++ return
send \"\r\"
expect {
Prompt2> {send \"dir\r\" }
}
"
回答2:
return = fail
return
didn't work for me because in my case, it was not a shell that can simply prompt me again with the same question. I couldn't figure out how to get it to match on what was printed before I did return
.
expect_out (to fix above solution) = fail
The manual says:
Upon matching a pattern (or eof or full_buffer), any matching and previously unmatched output is saved in the variable expect_out(buffer).
But I couldn't get that to work (except where I used it below, combined with -indices
which makes it work there, and no idea how to make it work to get previous output fed into a new expect { ... }
block.)
expect_user
And the solution here using expect_user
didn't work for me either because it had no explanation and wasn't used how I wanted, so didn't know how to apply this limited example in my actual expect file.
my solution
So what I did instead was avoid the interactive mode, and just have a way to provide input, one line at a time. It even works for arrow keys and alt+...
, (in dpkg Dialog questions) but not for simply <enter>
sometimes (hit alt+y for <Yes>
or alt+o for <Ok>
for those in dpkg Dialog). (anyone know how to send an enter? not '\n', but the enter key like dpkg Dialog wants?)
The -i $user_spawn_id
part means that instead of only looking at your spawned process, it also looks at what the user types. This affects everything after it, so you use expect_after
or put it below the rest, not expect_before
. -indices
makes it possible to read the captured part of the regular expression that matches. expect_out(1,string)
is the part I wanted (all except the colon).
expect_after {
-i $user_spawn_id
# single line custom input; prefix with : and the rest is sent to the application
-indices -re ":(.*)" {
send "$expect_out(1,string)"
}
}
Using expect_after
means it will apply to all following expect
blocks until the next expect_after
. So you can put that anywhere above your usual expect
lines in the file.
and my case/purpose
I wanted to automate do-release-upgrade which does not properly support the usual Debian non-interactive flags (see here)...it just hangs and ignores input instead of proceeding after a question. But the questions are unpredictable... and an aborted upgrade means you could mess up your system, so some fallback to interaction is required.
回答3:
Thanks Mike for that suggestion. I tweaked it a bit and adapted it to my problem.
Changed code:
expect Prompt1>
interact timeout 10 return
expect {
timeout {exp_continue}
Prompt2 {send \"dir\r\" }
}
The timeout 10 value is not related to the set timeout -1
we set initally. Hence I can execute whatever commands I want on Prompt1
and once keyboard is idle for 10 seconds then script gains control back.
Even after this I faced one more problem, After Prompt1
, I wanted to execute command to load a particular image. The image loading takes around 2 minutes. Even with set timeout -1
the script was timing out waiting for Prompt2
. It's not the telnet timeout even, which i verified. But the solution for this is the adding exp_continue in case of timeout within the expect statement.
For your set timeout -1
to take into effect it should be placed before the spawn telnet command within expect.
来源:https://stackoverflow.com/questions/5602327/how-do-i-tell-expect-that-i-have-finished-the-interactive-mode