expect program send commands from file - linux

How to make send command of "expect" program to read from a file and use each line as argument.
I want to use a loop like structure in expect program which may look like below(NOTE:- while loop is imaginary.)
spawn /my/program
expect {
-re EBtxjjmEcQTxc0SLd4TdXxjUduxCOLZBwEme2Z.*password: {
while read_line in FILE;
do
send $read-line;
done
}
How to program the while-loop part equivalent using "expect"

Note in your question, you were missing a close brace, and you mis-typed your variable name (read_line and read-line)
Expect is a Tcl extension, so you have all the Tcl commands at your disposal
spawn /my/program
expect {
-re EBtxjjmEcQTxc0SLd4TdXxjUduxCOLZBwEme2Z.*password: {
set fh [open FILE r]
while {[gets $fh read_line] != -1} {
send "$read_line\r"
}
close $fh
}
}
If you install tcllib, you can do
package require fileutil
spawn /my/program
expect {
-re EBtxjjmEcQTxc0SLd4TdXxjUduxCOLZBwEme2Z.*password: {
fileutil::foreachLine read_line FILE {
send "$read_line\r"
}
}
}

Related

How to suppress errors/outputs from a TCL thread?

I have created a thread :
set t1 [thread::create]
thread::send $t1 {
proc myProc {command args} {
exec {*}[auto_execok $command] {*}$args >& /dev/null
}
}
And then tried to send an asynchronous command :
thread::send -async $t1 [list myProc <command args>]
But the error/output from the command is getting displayed in output.
How to hide the errors/outputs from the command that is send to the async thread ?
The simplest method is to catch the errors.
thread::send $t1 {
proc myProc {command args} {
catch {
exec {*}[auto_execok $command] {*}$args >& /dev/null
}
}
}
Be aware that this makes any problems significantly more difficult to debug! It's better if you can identify what errors you expect and just try to catch them, so unexpected errors are still things you see and have to handle.
thread::send $t1 {
proc myProc {command args} {
try {
exec {*}[auto_execok $command] {*}$args >& /dev/null
} trap CHILDSTATUS {} {
# Ignore a non-zero exit?
}
}
}
For this specific case (which is very much I/O-bound from Tcl's perspective) you might be better off just adding & to the end of the exec call and not running in a separate thread. Can't really say for sure without knowing exactly what you're doing, but it is worth considering.

expect error handling - spawn id not open

I'm writing an expect script which can log out in hundreds of routers and change their config.
My problem is, there is a bug on the routers firmware which causes them to close the connection after the password is send.
If I log in again, it works perfectly (so only the first log in after reboot causes the exception).
When the connection is closed the expect script is terminated.
I would like if i could gracefully catch the exception, and try again.
The code which fails is this part:
# go through each IP
for {set i $start} {$i <= $end} {incr i} {
set ip "10.$octet2.$i.x"
puts "\n\n\n#### doing $ip...\n" ; flush stdout
# log in to the IP
spawn ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -l $user $ip
expect {
"continue connecting (yes/no)?" { send "yes\r" ; exp_continue }
"login as: " { send "$user\r" ; exp_continue }
"Password: " { send "$pwd\r" }
"No route to host" { continue }
timeout { continue }
}
# execute commands from file
foreach c "$commands" { eval $c }
}
The error I get looks like this:
Password:
Connection to 10.x.x.x closed by remote host.
Connection to 10.x.x.x closed.
send: spawn id exp11 not open
while executing
"send "exit\r""
("eval" body line 1)
invoked from within
"eval $c "
("foreach" body line 1)
invoked from within
"foreach c "$commands" { eval $c }"
("for" body line 18)
invoked from within
"for {set i $start} {$i <= $end} {incr i} {
set ip "10.$octet2.$i.x"
puts "\n\n\n#### doing $ip...\n" ; flush stdout
# log in to the IP
spa..."
(file "./multido.exp" line 39)
Any help is really appreciated!
You can catch the exception using the tcl command catch to surround the command that might error. You would extend your code's inner loop to resemble this:
set tryrun 1
while {$tryrun} {
spawn ssh ...
expect ...
set tryrun 0
foreach c "$commands" {
if {[catch {eval $c} result]} {
puts "failed: $result"
set tryrun 1
}
}
}
Perhaps a simpler solution would be to look for the pattern "closed by remote host" in your expect, and using this to repeat a similar loop.

How to handle a hang scenario in perl? After accessing a file

I need to access a file that is in a nfs mountpath.
After I access, I need to see a hang. If the hang succeeds, then my scenario passes.
If I see a "permission denied" or if access succeeds, the scenario fails.
How do I hadle the hang? After hang, how do I exit/kill that operation and proceed with my program's next set of steps. I am currently doing this.
Can I do something like this if(sleep = 10 seconds) {
The subroutine takes the command to execute, file path.
sub access_timeout($$) {
my $cmd = shift;
my $file_path = shift;
print(qq{Running the command "$cmd $file_path" on the client});
# Here, I need to handle sleep. Sleep is expected case here. something like if ($result = sleep(10)) { success}
my $output = $client=>execute(command => qq{$cmd $file_path && echo _OK_});
if ($output =~ /(.*)_OK_/s) {
croak(qq{Expected a hang, access should not work});
} elsif ($output =~ /permission denied/s || $output =~ /No such file or directory/s) {
croak(qq{expected a hang but there is response});
}
}
Try alarm. This will throw a signal, identified as SIGALRM. All the rest can be gotten from the link.

How to use expect with optional prompts?

Let's say I am trying to write an expect script for a test.sh that has three prompts: prompt1, prompt2, prompt3.
My code is like this:
spawn test.sh
expect "prompt1"
send "pass1"
expect "prompt2"
send "pass2"
expect "prompt3"
send "pass3"
However, prompt2 only occurs half the time. If prompt2 doesn't show up, the expect script breaks. How would I write expect code that skips over prompt2 if it doesn't show up?
EDIT:
Fixed my code:
/usr/bin/expect -c '
spawn ./test.sh
expect {
"prompt1" {
send "pass1\r"
exp_continue
}
"prompt2" {
send "pass2\r"
exp_continue
}
"prompt3" {
send "pass3\r"
exp_continue
}
}
interact return
'
This way, the rest of the script executes and provides output.
As long as you have a case that will be always be expected to hit and don't include an exp_continue in that case, you can can remove duplication and handle optional prompts easily:
expect "prompt1"
send "pass1"
expect {
"prompt2" {
send "pass2"
exp_continue
}
"prompt3" {
send "pass3"
}
}
You can expect multiple things:
expect {
"prompt2" {
send "pass2"
expect "prompt3"
send "pass3"
}
"prompt3" {
send "pass3"
}
}

How to return spawned process exit code in Expect script?

I use expect for running test scripts.
Tests return success/failure through exit code. But expect return equivalent exit code.
How to make expect return proper exit status?
My tests are sql scripts run with psql (postgresql command processor).
Since psql doesn't allow to specify database password as a command line parameter, expect scripts do that.
So, my expect script looks like:
spawn $SPAWN_CMD
expect {
-re "Enter password for new role:" {
send "$PWPROMPT\n"
exp_continue
} -re "Enter it again:" {
send "$PWPROMPT\n"
exp_continue
} -re "Password(.*)" {
send "$PASSWORD\n"
exp_continue
} -re "Password(.*):" {
send "$PASSWORD\n"
exp_continue
} eof
}
You're already waiting for the eof at the end of your loop, you just need to use wait and catch the result:
spawn true
expect eof
catch wait result
exit [lindex $result 3]
Exits with 0.
spawn false
expect eof
catch wait result
exit [lindex $result 3]
Exits with 1.

Resources