Expect Script - varying sends - shell

I'm working on expect script to call a installer.sh
For a neat installations , it works fine.
But when the installer fails for pre-checks, the order of the send differs and i have no control on the order.
spawn ./Installer.sh
expect "change? (Y/N)"
send "Y\r"
expect "path"
send "$path\r"
expect "Enter selection"
send "1\r"
expect "path"
send "$path
exit 0
After 2nd expect "path", the installer validates internally and proceeds with step3 and continues and finishes.
But if after second expect path , installer pre-checks fails then it exists and prompts for last step ie for the path again.
currently when the script displays after exiting and prompts for 4th, it continues to send the 3rd response which is irrelevant.Does the script does not validate fr the match expect string?
Error :
"No Space. Exiting.
**Path: 1**
cp: cannot create regular file `1': Permission denied
send: spawn id exp5 not open
while executing
"send "path\r""
The shell script exits for various reasons and prompts for last send.
Is there a way to get the last display message expect_out while the session is ongoing and read it and continue based on it.
spawn shellscript
expect_1
send_1
expect_2
send_2
--sh stops and displays exiting...
if expect_out(buffer)=exiting
then
expect_4
send_4
else
expect_3
send_3
expect_4
send_4
exit

The key thing you probably need is the ability to expect several different things at once. Fortunately, this is quite easy to do.
expect {
"change? (Y/N)" {
# Something in here to respond to this case
# This bit is just code, but could be effectively empty too
}
"Exiting" {
# Now we've detected that the installer failed
send_user "oh no!\n"
exit 1
}
}
When using this form, you can restart the current expect from within a handler script by finishing it with exp_continue. As usual, you've got to think careful about what actual patterns to match.

Related

Using expect script inside started telnet session

I am using an expect script to interact with telnet session, which started earlier from bash. The script dont listen for symbols in command line. It just send commands after timeout runs out. I am sure, that i entered the right symbol to expect (), because the script works, when i run it standalone.
I have list of command (1 command per line) to send to mikrotik. My expect script puts every command to same line and then the script send them. I separated commands with ; and it worked, but just for a short command. So I decided to separate file with commands to smaller files. Then I open telnet session (in bash) and start expect script, which opens smaller files and send commands over and over. So I don´t need to restart same session over and over. Problem is, that expect script don´t send command, when it gets awaited symbol, but it sends commands after a timeout runs out.
This is part of bash script. It could be easier, to do whole script in Expect, but I didn't add loop and if condition yet and when i tried to create some while or if structure, I failed in it.
(
echo open "12.12.13.44"
sleep 2
echo "admin+t"
sleep 2
echo "admin"
sleep 2
/usr/bin/expect /home/toor/expect2.sh
) | telnet
Here is expected symbol
expect "> "
and here is the part of expect script, which puts all commands to same line (without sleep 1 command, script sends quit in the middle of reading from file)
set timeout 1
set fid [open /home/toor/file.txt]
set content [read $fid]
close $fid
set records [split $content "\r"]
foreach record $records {
lassign $records \
commands
expect "> "
send "$commands\r"
}
sleep 1
expect "> "
send "quit\r"
I will be grateful for any advice regarding my problem with the expecting for symbol, as well as with writing to same line.
You are piping the output of expect into telnet; there is no way for expect to receive information back from the telnet process in this scenario.
I would approach this as a chain of expect scripts instead - write one which starts the session, then hands over control to your existing script.

Expect script conditional scenario [duplicate]

This question already has answers here:
Using conditional statements inside 'expect'
(3 answers)
Closed 6 years ago.
I am trying to use the expect utility to automate a task which requires inputting the password during script execution.
expect -c "set timeout 120;
spawn /bin/bash /home/user/script.sh;
# Expect 1 - not expected always
expect \"Are you sure you want to continue connecting (yes/no)?\"; send "yes\r";
# Expect 2 - not expected always
expect \"Please type 'yes' or 'no':\"; send \"yes\r\";
# Expect 3 - this is required always to input the password
expect \"user#xyz.com's password:\"; send \"password\r\"; expect eof"
From the script above, expect 1 and 2 conditions are not required always. Its only needed when executing the script against new servers. Once the identity of the host is added to known hosts, the first 2 expect conditions wont be prompted by the script. Expect 3 is the only condition to be met in most of the cases.
In a system, where the host identity already exist, the expect script executes the first condition and stays until the timeout is reached and doesn't get to expect 3. I am not sure how to achieve this, but is there a way in expect to break if a prompt is not met.
The expect command has a time out, which I forgot how long, but just a couple of seconds. If not found what you are expecting, time out occurs and the expect command will exit. With that in mind, I have set the timeout to 1 second and seems to work. You might want to adjust it to your system.
#!/usr/bin/env tclsh
package require Expect
spawn /bin/bash /home/user/script.sh
# Time out (in seconds) for the expect commands
set timeout 1
expect "Are you sure you want to continue connecting (yes/no)? " { send "yes\r" }
expect "Please type 'yes' or 'no':" { send "yes\r" }
expect "user#xyz.com's password:" { send "password\r" }
interact
A couple of notes:
This script is not a bash script. It is a TCL script with Expect package. I recommend doing thing this way as TCL is very capable, yet not too hard to learn.
The timeout value is the the key here. You might even want to set different time-out values before each expect command to tailor your script's response time.
Setting timeout to 120 is too long: Each expect command will have to wait 2 minutes before moving on, at which time, your system might timed out before you can get to the next expect command.
Good luck.

Set OS X password using expect

I'm trying to write a script to update the password of an OS X account to a rotating, centrally-stored value. As a prelude to learning to use tclcurl, I just want to get this prototype script working:
#!/usr/bin/expect
set mgrpassword "newpassword" # this will become a tclcurl command later
spawn passwd manager
expect "New password:"
send "$mgrpassword\n"
expect "Retype new password:"
send "$mgrpassword\n"
puts "\nManager password changed."
exit 0
It runs without errors, but it does nothing; the password for the manager account remains unchanged. I've tried it with both \r and \n but that didn't make any difference. Can anyone see what I'm doing wrong or what steps I'm omitting?
(It will always run with admin rights; that's why there is no 'expect "Old password:"' line.)
Just add one more expect statement at the end, like as follows,
send "$mgrpassword\r"
expect eof
Basically, Expect will work with two feasible commands such as send and expect. If send is used, then it is mandatory to have expect (in most of the cases) afterwards. (while the vice-versa is not required to be mandatory)
This is because without that we will be missing out what is happening in the spawned process as Expect will assume that you simply need to send one string value and not expecting anything else from the session.
Your script can be written in the following way as well which makes it robust with the use of exp_continue. It will make the Expect to run again.
set mgrpassword "newpassword"
spawn passwd manager
expect {
timeout { puts "Timeout happened";exit 0}
"password:" {send "$mgrpassword \r";exp_continue}
eof {puts "Manager password changed"; exit 1}
}
So it turns out that
dscl . passwd /Users/manager [passwordstring]
works a lot better/easier than trying to combine passwd with expect. Hope this helps someone else.

Can the expect script continue to execute other command after "interact"?

I am writing a script to run ssh so as to login a remote host, after all the operation is done, I type exit and log off. But I want the script to continue running and write log on the local host. The script is something like:
#!/usr/bin/expect
spwan ssh qwerty#remote_host
expect {
"password:" {
send "123123\r"
}
}
interact;
send "echo $(date) >> login_history.log\r"
But the last command "send ..." always failed with the error message like
"send: spawn id exp4 not open ..."
When I log off from the remote host, can the expect script continue to work as it is running on the local host?
YES, processing can continue after an [interact].
Short answer: change the last {send ...} to {exec date >> login_history.log}
There are several concepts you'll want to understand to achieve the control flow you're after. First, http://www.cotse.com/dlf/man/expect/interact_cmd_desc.htm provides a succinct synopsis and example of intermediate [interact] use.
Second: why did you see the message "... spawn id ... not open ..."? Because the spawn id is not open. The script you wrote said, in effect, "interact, then, after interact is over, send a new command to the ssh process." If you've already logged out, then, of course that id for a defunct process is no longer available.
Third: how do you achieve what you want? I'm unsure what you want. It sounds as though it would be enough for you simply to transform the [send] as I've described above. How does that look to you?

How do I tell expect that I have finished the interactive mode?

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?
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\" }
}
"
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.
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.

Resources