I am currently encountering a problem.
When I want to download a file on a mikrotik in 6.48.6 using mtlogin and fetch tool, it works perfectly and the script waits until the router has finished downloading to send a "quit".
However, when trying the same manipulation on a router in version 7.1.5, the "quit" is sent directly, thus stopping the download because of the letter Q and thus sending "uit" thereafter in the prompt.
The prompts are similar for 6.48.6 and 7.1.5, and even when trying to add expects in the script, the result is the same.
I think the problem is in this part of the code, but don't know how to fix it.
# Run commands given on the command line.
proc run_commands { prompt command } {
global do_interact in_proc
set in_proc 1
# escape any parens in the prompt, such as "(enable)"
regsub -all "\[)(]" $prompt {\\&} reprompt
# handle escaped ;s in commands, and ;; and ^;
regsub -all {([^\\]);} $command "\\1\u0002;" esccommand
regsub -all {([^\\]);;} $esccommand "\\1;\u0002;" command
regsub {^;} $command "\u0002;" esccommand
regsub -all {[\\];} $esccommand ";" command
regsub -all {\u0002;} $command "\u0002" esccommand
set sep "\u0002"
set commands [split $esccommand $sep]
set num_commands [llength $commands]
for {set i 0} {$i < $num_commands} { incr i} {
send -- "[subst -nocommands [lindex $commands $i]]\r"
if { [lindex $commands $i] == "/system/reboot"} {
send "y\r"
}
expect {
-re "^\[^\n\r]*$reprompt" {}
-re "^\[^\n\r ]*>>.*$reprompt" { exp_continue }
-re "\[\n\r]+" { exp_continue }
}
}
if { $do_interact == 1 } {
interact
return 0
}
send "quit\r"
expect {
-re "^WARNING: There are unsaved configuration changes." {
send "y\r"
exp_continue
}
"\n" { exp_continue }
"\[^\n\r *]*Session terminated" { return 0 }
timeout { catch {close}; catch {wait};
return 0
}
eof { return 0 }
}
set in_proc 0
}
That's how it looks like
Does anyone have a solution?
I just find the solution in mtlogin at line 625!
foreach router [lrange $argv $i end] {
set router [string tolower $router]
send_user "$router\n"
# Figure out prompt.
set prompt "] > " #Just added a second whitespace after >
# alteon only "enables" based on the password used at login time
set autoenable 1
set enable 0
Hope it's gonna help you
Related
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.
Because my company insists on using sudo I now have to scrap my ssh system I've built for running remote commands.
I have a script that will connect to a server, sudo into the db2 inst owner account and then run a script (previously delivered into /tmp). Maybe 1 in 10 times it will work (outputs 'Hello world' for my test).
Here is the ksh script on the remote server (/tmp/cwow/generic.ksh):
#!/usr/bin/ksh93
echo "Hello world"
[I've also tried adding sleep 5 and wait with mixed results but it doesn't solve the problem]
The expect script I'm running locally is:
#!/usr/bin/expect
set spath /tmp/cwow/generic.ksh
set pass $env(MYEXPECTPASS)
set user $env(MYEXPECTUSER)
if { [llength $argv] != 2 } {
send_user "USAGE: $argv0 host inst\n"
exit
}
set host [lindex $argv 0]
set inst [lindex $argv 1]
set timeout 10
log_user 1
exp_internal 0
eval spawn /usr/bin/ssh -t $user#$host "sudo su - cwow"
expect {
timeout { send_user "TimedOut"; exit }
-glob "assword:" {
send "$pass\r"
expect {
-glob "assword:" {
send "$pass\r"
expect {
-glob " " {
send "/tmp/cwow/generic.ksh\r\n"
expect {
-glob "world" {
send_user "Got it\r"
}
}
}
}
}
}
}
}
I should also note that I never get the 'Got it' message but I don't really need that to work, just curious why it doesn't. What I need to work reliably is for the script to run and, most of the time, it doesn't appear to.
Any ideas for a weak expect user would be greatly appreciated.
(not an answer, just a formatted comment)
You don't need to nest all the expect commands: if you expect a pattern with no action body, the script will continue to the next command. This is more readable, IMO:
expect {
timeout { send_user "TimedOut"; exit }
"assword:"
}
send "$pass\r"
expect "assword:"
send "$pass\r"
expect " "
send "/tmp/cwow/generic.ksh\r"
expect "world"
send_user "Got it\n"
Note, you should send to the spawned process with \r as "hitting Enter". But \n is used for send_user.
I have make an example as below. The password(mingps)is the shell variable. When execute the shell script, in the mean while, execute command "ps -ef", I found the result of "ps" showed the password(mingps). For security reason, I don't want to show the password when execute command "ps -ef". So how to hide it? Thanks in advance.
#!/bin/sh
MalbanIP="XXX.XXX.XXX.XXX"
MalbanLogin="ming"
MalbanPwd="mingps"
MalbanCmd="netstat"
firstTime="true"
/usr/bin/expect <<EOF
set timeout 10
log_user 0
spawn /usr/bin/ssh $MalbanIP -l $MalbanLogin
expect {
-nocase "continue connecting (yes/no)?" {
send "yes\r"
expect "password:" {
send "$MalbanPwd\r"; set firstTime "false"; exp_continue
}
}
"password" {
if {$firstTime == "true"} {
send "$MalbanPwd\r"; set firstTime "false"
} else {
log_user 1; puts stdout "password is wrong"; log_user 0;
exit 1
}
}
}
expect "0-0-3"
log_user 1
send "$MalbanCmd \r"
set results \$expect_out(buffer)
expect "0-0-3" { send "exit\r" }
expect eof
EOF
exit 0
Option 1
The best way is to switch to using RSA keys to log in, as this will enable you to significantly strengthen your overall system security substantially. With that, you can probably avoid using Expect entirely.
Option 2
However, if you can't do that, the key to fixing things is to not pass it as either an argument or an environment variable (since ps can see both with the right options). Instead, you pass the password by writing it into a file and giving the name of that file to the Expect script. The file needs to be in a directory that only the current user can read; chmod go-rx will help there.
MalbanPwdFile=/home/malban/.securedDirectory/examplefile.txt
# Put this just before the spawn
set f [open $MalbanPwdFile]
set MalbanPwd [gets $f]
close $f
You might also need to put a backslash in front of the use of $MalbanPwd so that it doesn't get substituted by the shell script part too early.
Option 3
Or you could stop using that shell wrapper and do everything directly in Tcl/Expect.
#!/usr/bin/expect
set MalbanIP "XXX.XXX.XXX.XXX"
set MalbanLogin "ming"
set MalbanPwd "mingps"
set MalbanCmd "netstat"
set firstTime true
set timeout 10
log_user 0
spawn /usr/bin/ssh $MalbanIP -l $MalbanLogin
expect {
-nocase "continue connecting (yes/no)?" {
send "yes\r"
expect "password:" {
send "$MalbanPwd\r"
set firstTime false
exp_continue
}
}
"password" {
if {$firstTime} {
send "$MalbanPwd\r"
set firstTime false
} else {
log_user 1
puts stdout "password is wrong"
log_user 0
exit 1
}
}
}
expect "0-0-3"
log_user 1
send "$MalbanCmd \r"
set results \$expect_out(buffer)
expect "0-0-3" { send "exit\r" }
expect eof
I suspect that this last option will work best for you in the longer term. It's definitely the simplest one (other than switching to RSA keys, which is what I've got deployed on my own infrastructure) and I think it is going to avoid some subtle bugs that you've got in your current code (due to substitution of variables at the wrong time).
I am trying to install a software which is a shell script file. I am using expect to do the silent installation.
There is a strange line during the installation of the software where the prompt goes to the next line like this below.
ENTER AN ABSOLUTE PATH, OR PRESS <ENTER> TO ACCEPT THE DEFAULT
:
So I have tried with these 2 options, but it's NOT working!
1. expect " : " { send "/home/tester/IDir\r" }
2. expect "ENTER AN ABSOLUTE PATH, OR PRESS <ENTER> TO ACCEPT THE DEFAULT\
: " { send "/home/tester/IDir\r" }
Expect File
#!/usr/bin/expect -f
#Taking arguments from test.sh file
set File [lindex $argv 0]
set IDir [lindex $argv 1]
spawn sh /home/tester/$File
expect "PRESS <ENTER> TO CONTINUE: " { send "\n" }
exp_internal 1
expect -re "TO ACCEPT THE DEFAULT\r\n.*?:" { send "/home/tester/IDir\r" }
#expect " : " { send "/home/tester/IDir\r" }
expect "IS THIS CORRECT? (Y/N): " { send "Y\n" }
expect "PRESS <ENTER> TO CONTINUE: " { send "\n" }
sleep 2
expect "ENTER THE NUMBER OF THE DESIRED CHOICE: " { send "2\n" }
expect "PRESS <ENTER> TO EXIT THE INSTALLER: " { send "\n" }
sleep 2
expect eof
test.sh file
dir=James/SDD
cd /home/tester
file=`ls | grep xx_yy*_linux_x86-64.bin`
expect script.exp $file $dir
After analyzing the logs, realized that your expect code is getting timed out for most of the cases.
# This timeout is meant for the first expect word 'PRESS <ENTER> TO CONTINUE', so it is proceeding to the next expect word 'TO ACCEPT THE DEFAULT.*?:'
expect: timed out
Gate keeper glob pattern for 'TO ACCEPT THE DEFAULT
.*?: ' is 'TO ACCEPT THE DEFAULT
You need to add the proper timeout handling for the expect statement. Default timeout is 10 seconds. You can change it in a way as follows,
set timeout 120; # Now, timeout value is 2 min
I can see that you have used sleep in some places, in the same way, do that for earlier expect commands as well. (Using timeout would be better in such cases than sleeping.)
#Taking arguments from test.sh file
set File [lindex $argv 0]
set IDir [lindex $argv 1]
# A common handler for timeout.
# Customize it as per your need.
proc my_timeout_handler {} {
puts "Timeout happened :("
exit 1
}
spawn sh /home/tester/$File
set timeout 300; # 5 mins.
expect {
"PRESS <ENTER> TO CONTINUE: " { send "\n" }
timeout {my_timeout_handler}
}
expect {
-re "TO ACCEPT THE DEFAULT\r\n.*?:" { send "/home/tester/IDir\r" }
timeout {my_timeout_handler}
}
expect {
"IS THIS CORRECT? (Y/N): " { send "Y\r" }
timeout {my_timeout_handler}
}
expect {
"PRESS <ENTER> TO CONTINUE: " { send "\r" }
timeout {my_timeout_handler}
}
expect {
"ENTER THE NUMBER OF THE DESIRED CHOICE: " { send "2\r" }
timeout {my_timeout_handler}
}
expect {
"PRESS <ENTER> TO EXIT THE INSTALLER: " { send "\r" }
timeout {my_timeout_handler}
}
expect {
eof {puts "Program completed"}
timeout {my_timeout_handler}
}
Is it possible to spawn an application, send commands, expect results, but also 'respawn' the application in case it crashed and continue from the last point? I tried creating procedures for spawning, but I am not able to catch user shell prompt once the application gets closed
So it sounds like you are doing something like telneting or sshing
into a shell then running an application. The application dies or
hangs so the prompt does not return so expect does not return and so
you are stuck. You will need to use timeout to detect a hang and restart
your entire process ( including the spawn). Below is the boiler plate I start with when writing
expect scripts. The critical thing to help you resolve your problem
is to realize that spawn not only sets the spawn_id but also returns
the pid of the spawned process. You can use that pid to kill the
spawned process if you get an eof and/or a timeout. My boiler plate
kills the spawn process on timeout. The timeout does not bail from the
expect loop but waits for the eof before exiting. it also accumulates
the output of expect command so on timeout you may be able to see
where it dies. The boilerplate is in a proc called run. Spawn the process
and pass the pid and the spawn id . Reuse the boiler plate to define other
procs that will be the steps. put the procs in a script with a counter between
them as shown and repeat. The other answerer is correct that the unless the app
restarts where you left off you can need to start from scratch. If it does start from
where you left off. make the steps granular enough that you know what command
to repeat. and step to start from.
BoilerPlate
proc run { pid spawn_id buf } {
upvar $buf buffer; #buffer to accumulate output of expect
set bad 0;
set done 0;
exp_internal 0; # set to one for extensive debug
log_user 0; # set to one to watch action
expect {
-i $spawn_id
-re {} {
append buffer $expect_out(buffer); # accumultate expect output
exp_continue;
}
timeout {
send_user "timeout\n"
append buffer $expect_out(buffer); # accumultate expect output
exec kill -9 $pid
set bad 1
exp_continue;
}
fullbuffer {
send_user " buffer is full\n"
append buffer $expect_out(buffer); # accumultate expect output
exp_continue;
}
eof {
send_user "Eof detected\n"
append buffer $expect_out(buffer); # accumultate expect output
set done 1 ;
}
}
set exitstatus [ exp_wait -i $spawn_id ];
catch { exp_close -i $spawn_id };
if { $bad } {
if { $done } {
throw EXP_TIMEOUT "Application timeout"
}
throw BAD_ERROR "unexpected failure "
}
return $exitstatus
}
set count 0
set attempts 0 ; # try 4 times
while { $count == 0 && $attempts < 4 } {
set buff ""
set pid [spawn -noecho ssh user#host ]
try {
run $pid $::spawn_id buff
incr count
run2 $pid $::spawn_id buff
incr count
run3 $pid $::spawn_id buff
incr count
run4 $pid $::spawn_id buff
incr count
} trap EXP_TIMEOUT { a b } {
puts "$a $b"
puts " program failed at step $count"
} on error { a b } {
puts "$a $b"
puts " program failed at step $count"
} finally {
if { $count == 4 } {
puts "success"
} else {
set count 0
incr attempts
puts "$buff"
puts "restarting\n"
}
}
}