What is the proper way to script Expect? - expect

I am quite new to Expect and am looking for some suggestions on this one:
I am trying to ssh to the servers and execute some commands.
If the server is one that I am logging into for the first time, it prompts me whether I want to trust it. I select "yes"; this is why I have the first expect. The second expect is for the password.
When I do the following, it accepts either the first prompt or the second prompt, so this does not work for me.
expect {
"yes/no" {
send "yes\n"
}
"assword: " {
send "$mypass\n"
}
}
When I do the following, it works, but it waits for a very long time before it enters the password. I suspect it waits for the "yes/no" prompt for a certain amount of time, does not receive it and then moves on
expect "yes/no" {
send "yes\n"
}
expect "assword: " {
send "$mypass\n"
}
What is the right way to set this up?

When you split it into two separate commands, you force the script to first look for the "yes/no" prompt. It will wait up to $timeout seconds (default 10 seconds). Only then will it look for the password prompt.
Your first idea is the right approach, but you're missing one key command:
expect {
"yes/no" {
send "yes\r"
exp_continue ;# <== this
}
"assword: " {
send "$mypass\r"
}
}
The exp_continue command essentially forces the flow of execution to remain in that Expect command so you can still match the password prompt.
A minor point: idiomatically, use \r (carriage return) as the character for "hitting Enter".
For further learning, look at the expect tag for more information and links to other questions and answers.

Related

expect script bug with expect timeout, exp_continue, or multiple expect possibilities

I'd like for an expect script to start a ssh session, automatically answer "yes" if asked if you're sure, then at the password prompt, temporarily pass interaction to the user for them to enter their password, then to take it back after detecting the user hit enter. Oh, and to continually retry if there's a 3 second timeout, in case the ssh server on fileServer isn't up yet.
1: expect "root*archiso*# "
2: puts "\n<<< Make sure sshd is started on fileServer >>>\n"
3: set readyForPassword 0
4: while { $readyForPassword == 0 } {
5: send "scp guest#fileServer:/public/* /publicCopy\n"
6: expect -timeout 3 {
7: "Are you sure you want to continue connecting (yes/no)? " {
8: send "yes\n"
9: exp_continue
10: } "password: " {
11: set readyForPassword 1
12: }
13: }
14: }
15: interact "\n" return
16: send "\n"
What I think the code is going to do follows. Line 6 will look for a match on "Are you sure you want to continue connecting (yes/no)? " or "password: ". If it times out after 3 seconds, the while loop will restart, and scp will be tried again. If it matches the former line, it will send "yes\n" and look for an additional 3 seconds for "password: ". If it matches "password: " either the first time, or after sending "yes", it will exit the while loop.
Line 15 will let the user type in the password, and when enter is pressed, control will return back to the expect script. The user's enter doesn't get sent, so send an enter in its place.
My understanding is clearly off. sshd is started on fileServer, so it immediately gets to the password prompt. If I do nothing, in 3 seconds, it hits enter for me. Then it hits enter about once per second, until the third failure, when scp aborts. Then, it tries scp again, repeating the process forever. So, clearly, readyForPassword is never getting set to 1.
This type of while / readyForPassword loop works in other locations in my code, so it's something to do with timeout, exp_continue, or handling multiple possibilities for expect - those are the new things to me in this section of code.
I'm wondering if after "yes\n" is sent, when that triggers "password: " to be sent, if exp_continue isn't re-evaluating the new text including "password: ". But, if that's the case, I'm not sure how to fix it.
Also, I think it's not just hitting enter for the password for me, I'm pretty sure it's send the scp line as the password.
You should use carriage-return \r to match user's 'enter' key input, not the line-feed \n character. Change it as,
interact "\r" return
send \r
expect "root*archiso*# "
Also, it is recommended to use \r always, instead of \n.

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.

Handle multiple statements in an Expect script

I am new to Expect scripting.
I wrote an Expect script for ssh in a Linux machine, where I am facing a problem in ssh'ing to different Linux machines. Below I have copied the script.
!/usr/local/bin/expect
set LinuxMachine [lindex $argv 0]
spawn ssh root#$LinuxMachine
expect "root#$LinuxMachine's password:"
send "root123\n"
expect "[root#Client_FC12_172_85 ~]#"
send "ls"
interact
When I supply 10.213.172.85 from command line the expect in the 4th line, it reads as "root#10.213.172.85's password:" and logs in successfully
But some Linux will expect
The authenticity of host '10.213.172.108 (10.213.172.108)' can't be established.
RSA key fingerprint is da:d0:a0:e1:d8:7a:23:8b:c7:d8:40:8c:b2:b2:9b:95.
Are you sure you want to continue connecting (yes/no)
In this case the script will not work.
How can I have two Expect statements in one Expect command?
You can use exp_continue in such a case:
expect {
"Are you sure you want to continue connecting (yes/no)" {
send "yes\r"
exp_continue
}
"root#$LinuxMachine's password:" {
send "root123\r"
expect "[root#Client_FC12_172_85 ~]#"
send "ls\r"
interact
}
}
In the above, the Expect block waits for either the yes/no question OR the prompt for password. If the latter, it moves on with providing password, expecting prompt, sending ls command and giving control back.
If the former, it will answer 'yes' and repeat the expect block, ready to find the prompt for a password (or even the yes/no question again, for that matter - not that you will need that).
I would also include some timeouts with meaningful messages in case some expect option does not match as expected, but the above should work.
As a side comment, you don't want to set a root password in a script... I recommend using ssh key authentication.
We like to call it "long log in". There are ssh options that don't check the host keys:
send -- "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no username#host\n"
expect {
"Password" {
send -- "$passwd\n"
}
Part of the Bash script that calls on the expect sets the password:
echo -n "Enter LDAP password: "
read -s passwd
echo

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.

Command not write in buffer with Expect

I try to backup a Linkproof device with expect script and i have some trouble. It's my first script in expect and i have reach my limits ;)
#!/usr/bin/expect
spawn ssh #IPADDRESS
expect "username:"
# Send the username, and then wait for a password prompt.
send "#username\r"
expect "password:"
# Send the password, and then wait for a shell prompt.
send "#password\r"
expect "#"
# Send the prebuilt command, and then wait for another shell prompt.
send "system config immediate\r"
#Send space to pass the pause
expect -re "^ *--More--\[^\n\r]*"
send ""
expect -re "^ *--More--\[^\n\r]*"
send ""
expect -re "^ *--More--\[^\n\r]*"
send ""
# Capture the results of the command into a variable. This can be displayed, or written to disk.
sleep 10
expect -re .*
set results $expect_out(buffer)
# Copy buffer in a file
set config [open linkproof.txt w]
puts $config $results
close $config
# Exit the session.
expect "#"
send "logout\r"
expect eof
The content of the output file:
The authenticity of host '#IP (XXX.XXX.XXX.XXX)' can't be established.
RSA key fingerprint is XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.
Are you sure you want to continue connecting (yes/no)? #username
Please type 'yes' or 'no': #password
Please type 'yes' or 'no': system config immediate
Please type 'yes' or 'no':
Like you can see, the result of the command is not in the file. Could you, please, help me to understantd why ?
Thanks for your help.
Romuald
All of your "expect" statements are timing out because the text they are waiting for does not match the text that actually appears. Let's examine the first one or two, the others are all the same.
You say:
expect "username:"
But what it actually receives from ssh is:
The authenticity of host '#IP (XXX.XXX.XXX.XXX)' can't be established.
RSA key fingerprint is XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.
Are you sure you want to continue connecting (yes/no)?
This does not contain the string "username:" so the expect command will time out, and the script will move on to the next command:
send "#username\r"
We can see it does send that:
Are you sure you want to continue connecting (yes/no)? #username
But that's not a valid answer to this question.
And the rest of the output is the same idea over and over.
As #joefis mentioned, you do need to catch the yes/no from ssh.
I've copied this from my response in serverexchange as its highly relevant here
You will want to avoid using "Password:" if you monitor your strings
during login, you will find that it is not always capitalized.
Changing your expect to -re "(.*)assword:" or "assword:" tends to be
much more effective for catching the line.
If you find that the timings are still too quick you can put a sleep
1; before your send
This is what i use for expect
expect {
#When asked about authenticity, answer yes then restart expect block
"(yes/no)?" {
send "yes\n"
exp_continue
}
"passphrase" { send "\r" }
-re "(.*)assword:" { sleep 1; send -- "password\r" }
-re $prompt { return }
timeout { puts "un-able to login: timeout\n"; return }
eof { puts "Closed\n" ; return }
}
so a couple of things, this will allow for expect to respond to any of these results on a single expect, it will then only continue to more of the code if a return statement is found. I would recommend setting a prompt value as it becomes help full to detect if your commands are complete or your login was indeed successful.

Resources