I'm trying to write an expect script to:
Connect to a remote server providing the user and password
Loop through a local file reading each line
Execute a specific command on the remote server for each of those lines
I could successfully achieve the step #1 and was testing the #3 with a simple scenario, but couldn't make it work yet. Unfortunately in the line 8 of the script, after sending the password, I just get logged into the server as I would have been logged manually (I can interact with the console) and the rest is not executed.
How can I circumvent this problem?
This is the script:
#!/usr/bin/expect
set timeout 20
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh -t -t "$user\#$ip"
expect "Password:"
send "$password\r";
expect "NYXOOBPN402(config)$"
send "delete decoders:ASX-Trade24-FIX.mdp\r"
expect "Are you sure you want to delete 'decoders:ASX-Trade24-FIX.mdp' (y/n)?"
send "y\r";
And this is how I'm executing it:
./test_expect.sh 172.18.250.20 admin admin
The problem is that my expect was incorrect in the line 17. Instead of "NYXOOBPN402(config)$", I should just put "*(config)*", since there's a lot of text before this part that was not being matched.
This is my final script for someone that runs into this same issue:
#!/usr/bin/expect
set timeout 9
# Check if the parameters are correct
if {[llength $argv] == 0} {
send_user "Usage: ./test_expect.sh ip username password\n"
exit 1
}
# Read the file with all the decoders names to be deleted
set f [open "decoders.txt"]
set decoders [split [read $f] "\n"]
close $f
# debug mode - very useful:
#exp_internal 1
# Connect to the server
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh -t "$user\#$ip"
expect "Password: "
send "$password\r";
sleep 3
# send ctrl+c since this terminal shows a lot of decoders
#send \x03
expect {
default { send_user "\nCould not find the expected value.\n"; exit 1 }
"*(config)*" {
# Loop through all the decoders
foreach decoder $decoders {
#send_user "Removing $decoder\n"
send "delete decoders:$decoder\r"
expect {
"Are you sure you want to delete*" { send "y\r" }
"*decoder will still be active*" { send_user "\nRemoved $decoder successfully\n" }
"*no such file or directory" { send_user "\nDecoder $decoder already deleted.\n" }
default { send_user "\nNot expected value with $decoder, please debug.\n"; exit 1 }
}
}
}
}
Related
Is it possible to import common expect functions into another expect script?
The use case is I have a server accessed via serial console port, so there is a long list of steps to connect and access the serial console. There are several expect scripts that all need this initial setup and login. Ie a sample script (ignoring the serial connect steps) might be:
#!/usr/bin/expect
set host [lindex $argv 0]
set user [lindex $argv 1]
set pass [lindex $argv 2]
spawn ssh $user#$host
set timeout 5
# Login to host
expect {
"assword:" {
send -- "$pass\r"
}
}
expect {
// DO OTHER EXPECT STUFF
}
Is it possible to put the login into a common file that is imported/included in every script to setup the initial shell connection and login?
login.exp:
# Login to host
expect {
"assword:" {
send -- "$pass\r"
}
}
foo.exp:
#!/usr/bin/expect
set host [lindex $argv 0]
set user [lindex $argv 1]
set pass [lindex $argv 2]
spawn ssh $user#$host
set timeout 5
####include login.exp
expect {
// DO OTHER EXPECT STUFF
}
I am trying to write a script will provide credentials to a vpn with one caveat where VPN requires that I provide OTP.
This is my script:
#! /usr/bin/expect
set vpn [ lindex $argv 0]
set group [ lindex $argv 1]
set user [ lindex $argv 2]
set secret [ lindex $argv 3]
log_user 2
spawn nmcli con up $vpn --ask
expect {
"GROUP:" {
send "$group\r"
exp_continue
}
"Username:" {
send "$user\r"
exp_continue
}
Password: {
send_user "\nProvide RSA OTP:"
expect_user -re ":(.*)\r"
set otp $expect_out(0,string) <--------- Error!!!
set pass "$secret$otp"
send "$pass\r"
exp_continue
}
"Connection successfully activated" {
send_user "Connected to VPN\r"
}
"Login failed" {
send_user "Login failed\r"
exp_continue
}
"already active" {
send_user "Already connected to VPN\r"
}
}
exit
I included an arrow to what I think is the source of the problem because when I run this script, it looks like expect_out contains Password:. Given what i read in man page I imagined that the buffer is cleared each time new match is made and therefore it contains string matched in most recent expect clause( in this case expect_user -re ":(.*)\r"), however it seems that I'm wrong. I did not see any notes saying that expect_user contents need to be accessed using different function like interact does, so I'm not sure where did my password go?
Thank you
When using send_user you will not see an echo of what you write, unlike when you send to the spawned command, so you will never match : unless the user explicitly types it. Also, you will read newline, not carriage-return. So use
expect_user -re "(.*)\n"
to get the user input and $expect_out(1,string) (1 not 0) will contain the input.
What you are observing is a timeout in your expect_user, which is why the variable is not updated and still contains the old match.
To protect yourself against unseen timeouts, you could set up a "global" timeout check by starting with
proc abort { } { send_user "Timeout!" ; exit 2 }
expect_before timeout abort
The expect_before is done "before" each expect, but also before each expect_user, it seems.
I tried posting this in the codereview community, but there is no expect tag and I don't have enough karma to create tags.
I have written an expect script to either login to a server or run a simple (usually single) command and return the output.
I have two problems and a wish.
Commands that return nothing--i.e., ssh2server user host false--time out with an error (because I'm not capturing a timeout, though I suppose I should) instead of just returning nothing.
I can capture the return code of the program but I can't get it to exit with the appropriate code.
Is there a way I can take the output of the called program and return it the same way (remote stdout goes to local stdout and remote stderr goes to local stderr)?
Also, any comments or (constructive) criticisms would be appreciated.
#!/usr/bin/expect -f
if {[info exists ::env(SSH2SERVER_PASSWORD)]} {
set password "$env(SSH2SERVER_PASSWORD)"
} else {
puts "SSH2SERVER_PASSWORD not set"
exit 1
}
if {[llength $argv] < 2} {
puts "usage: ssh2server user server"
exit 1
}
set user [lindex $argv 0]
set server [lindex $argv 1]
set command [lrange $argv 2 end]
set pwd_prompt "*assword:"
set prompt "\$ "
set rc 0
expect_before {
#timeout { send_user 'timeout' ; exit 2 }
timeout { send_user 'timeout' ; set rc 2}
}
log_user 0
spawn ssh $user#$server
expect "$pwd_prompt" { send -- "$password\r" }
if { $command == "" } {
interact
} else {
expect {
"$prompt" {
send -- "PROMPT_COMMAND=\rPS1='_MYPROMPT_'\r$command\r"
#expect -re "$command\r\n(.*)\r\n\[^\r]*\[#\$%]"
expect -re "$command\r\n(.*)\r\n\[^\r]*_MYPROMPT_"
set results $expect_out(1,string)
puts $results
send -- "^D"
expect eof
#catch wait ec
#set rc [lindex $ec 3]
#puts [lindex $ec 3]
#exit [lindex $ec 3]
}
#eof { send_user $expect_out(buffer); exit 3}
eof { send_user $expect_out(buffer); set rc 3}
}
}
log_user 1
lassign [wait] pid spawnid os_flag rc
#puts $rc # outputs correct value
exit $rc
I suspect this is the problem:
send -- "^D"
You are not sending a Ctrl-D, you are sending the characters ^ and D.
To send a Ctrl-D
send -- "\04"
To solve the "no output, timeout" problem, you need to alter your expected regex: you have too many newlines for that case. Using expect -d would have revealed this to you. Like this:
send -- "unset PROMPT_COMMAND; PS1='_MYPROMPT_'\r"
expect -re "_MYPROMPT_$"
send -- "$command\r"
expect -re "$command(.*)\r\n_MYPROMPT_$"
The content of the capturing parentheses may now be empty.
I split off setting the prompt for clarity.
To capture the exit status of the command, you may have to do this:
send -- "$command; echo $?\r"
expect -re "$command(.*)\r\n(\d+)\r\n_MYPROMPT_$"
set results [regsub {^\r\n} $expect_out(1,string) ""]
set status $expect_out(2,string)
I don't think you can separate stdout and stderr with the expect command. I think both streams are captured as "output". (I don't have my Exploring Expect book nearby to confirm)
If that's important, you might want to invoke the command redirecting stdout and/or stderr to file(s), and then cat and capture the file contents.
I have the an expect script that starts like the following:
#!/usr/bin/expect -f
set timeout 1200
set prompt {[#$] }
set server [lindex $argv 0]
set username [lindex $argv 1]
set password [lindex $argv 2]
set package [lindex $argv 3]
spawn scp -o StrictHostKeyChecking=no "$package" ${username}#${server}:~
expect {
timeout { send_user "\nscp connect time out\n"; exit 1 }
"*assword"
}
after 3000
send "$password\r"
# wait for upload complete
expect {
timeout { send_user "\nscp timeout\n"; exit 1 }
eof
}
....
close
It errors out with the following message after ~15 s:
send: spawn id exp5 not open
while executing
"send "$password\r""
What could be the reason for this? What does exp5 mean?
I have also tried removing the "after 3000" statement, but I get the same error.
The connection is closed by the time you get to the point where you send your password. I suspect that you don't need to enter a password for that server (ssh keys are set up?), and by the time 3 seconds has elapsed (after 3000), the file transfer is complete.
You can condense your script somewhat:
#!/usr/bin/expect -f
set timeout 1200
set prompt {[#$] }
lassign $argv server username password package
# if your expect version does not have `lassign`, stick to below
#set server [lindex $argv 0]
#set username [lindex $argv 1]
#set password [lindex $argv 2]
#set package [lindex $argv 3]
spawn scp -o StrictHostKeyChecking=no "$package" ${username}#${server}:~
expect {
timeout {send_user "\nscp time out\n"; exit 1}
"*assword" {send "$password\r"; exp_continue }
eof
}
This loses the distinction between connection timeout and transfer timeout though.
I trying different approach to update existing expect script that I have inherit...
What I trying to achieve is to check Patch Level and depending on that, execute a set of commands.
But I can't make it work. :(
I tried as to below example and also tried with 'if' statement but I am clearly to fresh to solve this task.
I would be very grateful for some feedback what I am doing wrong.
#!/usr/bin/expect -f
# Destination: user#host
set destination [lindex $argv 0]
# Destination password
set pass [lindex $argv 1]
# Expect timeout
set timeout 1000
spawn ssh $destination
expect {
"hostname nor servname provided, or not known" {
exp_close
}
"yes/no)?" {
send "yes\n"
exp_continue
}
"assword:" {
send "$pass\n"
exp_continue
}
"#" {
log_file "./tmp/$destination.log"
send "cat /etc/*release | grep PATCHLEVEL\n"
expect {
"PATCHLEVEL = 3" {
send "zypper ar http://xxx.xxx.xxx.xxx/up2date/SLES11-SP3-LTSS-Updates/sle-11-x86_64/ SLES11-SP3-LTSS-Updates && zypper mr -r SLES11-SP3-LTSS-Update;zyppe
r ref -s;zypper --non-interactive update\n"
log_file
send "exit\n"}
"PATCHLEVEL =4 " {
send "zypper --non-interactive update\n"
log_file
send "exit\n"}
exp_close
}
}
}