Expect script conditional scenario [duplicate] - expect

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.

Related

Expect Script - varying sends

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.

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.

Expect Script - Starting logging from a certain point, and stop at a certain point

Hope you're all well!
I've recently begun putting together a little utility for a few our of network engineers to use. It's function is to retrieve circuit information which is then parsed and outputted in a nice format. Of course, part of this involves an SSH connection to the box required and running the command to generate the desired output.
Here's the script I've currently got. It works fine, runs the desired command, sends a few blank spaces to prompt the system to display more circuits, and then finishes off by exiting. All of this is being logged into a file with a name identical to the box's hostname. Great.
However my issue is apparent when I read the file produced and see that it includes a ton of data, including the command I ran and unnecessary stats provided on connection. I'm only looking for the logging to begin when I issue the command, and for it to cut off afterwards. As I'm not familiar with expect, I'd really appreciate any guidance.
PS. Apologies if I've done anything stupid; I'm pretty new to the language itself and the support out there isn't that great.
Thanks.
set ip [lindex $argv 0]
set hostname [lindex $argv 1]
set timeout 10
set user ""
set password ""
# Spawning the ssh session
spawn ssh $ip -l $user
# Awaiting prompt for password
expect "$user#$ip's password:"
sleep 1;
# Awaiting prompt
send "$password\n"
sleep 2
log_file -noappend $hostname;
send "terminal length 0\n"
sleep 1
send "show int desc\n"
sleep 5
send "exit\n"
interact
exit
You can control the output by means of placing the log_file in your desired place.
When you want to start logging, you can add the line as (which you already have in your code)
log_file my_log_file.log
Whatever printed in console will be logged after these command execution in to the file named my_log_file.log. By default, if the file is already present in the location, it will be appended. If you add the flag -noappend, then it will overwrite the existing file.
log_file -noappend fresh.log
If you want to stop the logging, then simply give the log_file without any arguments as such
log_file
From this point, whatever output generated will not be saved.
For example, you are logging to the some switch and giving some credentials and executing some commands can be something like as follows,
spawn ssh some_ip
expect some_thing
#Login code here
log_file output.log; # Start logging
#some actions here
log_file; # stopping logging here.
# some more actions
# end
You are sending the spaces for multiple times. Instead, you can set the terminal length to 0 as follows,
send "term len 0"
expect "prompt"
This will avoid the overhead of the sending it multiple times. Also, in your case, the spaces will be sent to the switch very fast, since there is nothing to expect. If you are still interested to do it without 'term len 0' , then at least you can put the code in a loop, like as follows,
for { set i 0 } { $i < 10 } { incr i } {
send " "
expect "something"
}
But, this way of doing is not advisable, since there is a possibility of need to send more than 10 spaces, then this will fail.

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.

SSH login with expect(1). How to exit expect and remain in SSH? [duplicate]

This question already has answers here:
Using expect to pass a password to ssh
(6 answers)
Closed 5 years ago.
So I wanted to automate my SSH logins. The host I'm with doesn't allow key authentication on this server, so I had to be more inventive.
I don't know much about shell scripting, but some research showed me the command 'expect' and some scripts using it for exactly this purpose. I set up a script and ran it, it worked perfectly to login.
#!/usr/bin/env expect -f
set password "my_password"
match_max 1000
spawn ssh -p 2222 "my_username"#11.22.11.22
expect "*?assword:*"
send -- "$password\r"
send -- "\r"
expect eof
Initially, it runs as it should.
Last login: Wed May 12 21:07:52 on ttys002
esther:~ user$ expect expect-test.exp
spawn ssh -p 2222 my_username#11.22.11.22
my_username#11.22.11.22's password:
Last login: Wed May 12 15:44:43 2010 from 20.10.20.10
-jailshell-3.2$
But that's where the success ends.
Commands do not work, but hitting enter just makes a new line.
Arrow keys and other non-alphanumeric keys produce symbols like '^[[C', '^[[A', '^[OQ' etc.[1]
No other prompt appears except the two initially created by the expect script.
Any ignored commands will be executed by my local shell once expect times out.
An example:
-jailshell-3.2$ whoami
ls
pwd
hostname
(...time passes, expect times out...)
esther:~ user$ whoami
user
esther:~ ciaran$ ls
Books Documents Movies Public
Code Downloads Music Sites
Desktop Library Pictures expect-test.exp
esther:~ ciaran$ pwd
/Users/ciaran
esther:~ ciaran$ hostname
esther.local
As I said, I have no shell scripting experience, but I think it's being caused because I'm still "inside of" expect, but not "inside of" SSH. Is there any way to terminate expect once I've logged in, and have it hand over the SSH session to me?
I've tried commands like 'close' and 'exit', after " send -- "\r" ". Yeah, they do what I want and expect dies, but it vindictively takes the SSH session down with it, leaving me back where I started. What I really need is for expect to do its job and terminate, leaving the SSH session back in my hands as if I did it manually.
All help is appreciated, thanks.
[1] I know there's a name for this, but I don't know what it is. And this is one of those frightening things which can't be googled, because the punctuation characters are ignored. As a side question, what's the story here?
I think your problem has been solved here before:
Using expect to pass a password to ssh
The command you're looking for is interact. It hands the control over to you/your keyboard.
I've used a similar script to autologin.
I used "interact" and I removed "expect eof". By doing this, I can get the screen back so that I can enter commands by hand.
expect "?assword: "
send -- "$password\r"
expect "$"
interact
putting it all together, log you in and leave you on the command line exactly as though you typed it manually
#!/usr/bin/expect -f
set ip "127.001.001.001"
set password "xxyykkx"
spawn ssh $ip -l root
expect "?assword:"
send "$password\r"
interact

Resources