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.
Related
I'm trying to build a script that gather information of our Cisco devices. I'm using bash and calling Expect in order to send command to the devices. I'm able to send command and get the output via the log function.
What I'd like to do is get the output of the line "Serial number: SERIAL_NUMBER"
Here is what I have so far:
/usr/bin/expect << EOF
set timeout 5
spawn ssh $Username#$IP ena\r$Password\rshow version \rexit\r
expect "password"
send "$Password\r"
expect -re {^[Serial Number: ].+}
puts "S/N: $expect_out(4,string)"
expect "*#"
expect eof
EOF
And I'm getting the following error:
S/N: (4,string)
expect: spawn id exp4 not open
I tried using other spawn_ID and still get the same error. I'm not 100% sure how tcl works so if someone could help me out.
Cheers,
I suspect that the problem is this:
expect -re {^[Serial Number: ].+}
puts "S/N: $expect_out(4,string)"
and that it should be:
expect -re {^[Serial Number: ](.+)}
puts "S/N: $expect_out(1,string)"
I've put a “capture group” in the regular expression, and I pick out that group for printing with expect_out(1,string) because it is the first such capture group; they're numbered from one (because zero is the entire matched string, which you usually don't need when working with expect).
Of course, that assumes that the code is working at all. If the other end of the connection has decided to tell you to go away, the expect script will fail. You should turn on the debugging mode for expect and see exactly what is going on. You should definitely check out the answer to this question for ways to debug that.
Expect Scripting Logging not finishing
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.
I have an expect script which I'd like to behave as a fancy ssh program that hops several machines and sets up the environment on target machine before running the commands.
I can use log_user 0/1 to turn off / on output from expect and that helps with password prompts and login banners, and commands to setup environment.
But, like ssh, once my script starts to issue commands, I don't want to see the issued command. That is I don't want to see "command" after send "command\n". All I want to see is the results of command.
How do I suppress the send output, but not the results?
Here's a snippet of the expect script:
log_user 1
foreach daline [lrange \$argv 0 end] {
send "\$daline\r"
set buffer1
}
So prior to this loop, I send password, and setup environment. Then in this loop, I run each bash command that was fed to the expect as an argument.
thanks.
Many programs echo their input. For example, if you send the date command to the shell, you will see the string date followed by a date. More precisely, you will see everything that you would ordinarily see at a terminal. This includes formatting, too.
send "date\r"
expect -re $prompt
The command above ends with expect_out (buffer) set to date\r\nFri Nov 7 20:47:32 IST 2014\r\n. More importantly, the string date has been echoed. Also, each line ends with a \r\n, including the one you sent with a \r. The echoing of date has nothing to do with the send command.
To put this another way, there is no way to send the string and have send not echo it because send is not echoing it in the first place. The spawned process is.
In many cases, the spawned process actually delegates the task of echoing to the terminal driver, but the result is the same-you see your input to the process as output from the process.
Often, echoed input can be handled by using log_user only (which you have used in different place). As an example, suppose a connection to a remote host has been spawned and you want to get the remote date, but without seeing the date command itself echoed. A common error is to write:
log_user 0 ;# WRONG
send "date\r" ;# WRONG
log_user 1 ;# WRONG
expect -re .*\n ;# WRONG
When run, the log_user command has no effect because expect does not read the echoed "date" until the expect command. The correct way to solve this problem is as follows:
send "date\r"
log_user 0
expect -re "\n(\[^\r]*)\r" ;# match actual date
log_user 1
puts "$expect_out(l,string)" ;# print actual date only
If you are sending a lot of commands to a remote shell it may be more convenient to just disable all echoing in the first place. You can spawn a shell and then send the command stty -echo, after which your commands will no longer be echoed. stty echo re enables echoing.
spawn ssh <host>
stty -echo; # Disable 'echo' here
expect something
#Your further code here
stty echo # Enable 'echo' here
#Close of connection
Reference : Exploring Expect
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.
I have the following bash script (script.sh):
#!/bin/bash
read -p "Remove? (y|n): " answer
echo "You answered '$answer'."
and I would like to drive it using expect. I have the following script (expect.exp, in the same directory):
#!/usr/bin/expect -f
set timeout -1
spawn -noecho ./script.sh
expect "^Remove"
send "y\r"
but it doesn't work as expected (pun intended). The result is:
~/Playground$ ./expect.exp
Remove? (y|n): ~/Playground$
So, the expect script somehow fails on the first 'expect "^Remove"' line and exits immediately, and the rest of script.sh does not execute. What am I doing wrong here?
I have been following the basic tutorials found online (the ones with the ftp examples). I am using expect 5.45 on Kubuntu 12.10.
Edit
So it changes if I add either 'interact' or 'expect eof' at the very end. But I have no idea what happens and why. Any help?
Two things I see:
"^Remove" is a regular expression, but by default expect uses glob patterns. Try
expect -re "^Remove"
while developing your program, add exp_internal 1 to the top of the script. Then expect will show you what's happening.
Ah, I see that expect adds special meaning to ^ beyond Tcl's glob patterns.
However, because expect is not line oriented, these characters (^ and $) match the beginning and end of the data (as opposed to lines) currently in the expect matching buffer
So what you see is that you send y\r and then you expect script exits as it has nothing more to do. When your script exits, the spawned child process will be killed. Hence the need to wait for the spawned child to end first: expect eof
Problem
You are not matching any text after the shell script's prompt, so the buffer for your spawned process never gets printed. The script finishes, the spawned process closes, and that's the end of the story.
Solution
Make sure you expect a specific response or your shell script's EOF, and then print your buffer. For example:
#!/usr/bin/expect -f
spawn -noecho "./script.sh"
expect "Remove" { send "y\r" }
expect EOF { send_user $expect_out(buffer) }