Use 'expect' in Bash with responses in any order - linux

I'm using this expect file (keygen.exp) to generate SSH key, I'm using ssh-keygen just as example to test 'expect':
#!/usr/bin/expect
spawn ssh-keygen
expect "Enter file"
send "./id_rsa\r"
expect "Enter passphrase"
send "\r"
expect "Enter same passphrase"
send "\r"
interact
It works perfectly, however, what if the order of the prompts for inputs is not the same every run, and there might be different questions on different runs?
I want something like this:
#!/usr/bin/expect
spawn ssh-keygen
expect if-is "Enter file"
send "./id_rsa\r"
expect if-is "Enter passphrase"
send "\r"
expect if-is "Enter same passphrase"
send "\r"
interact
Is it possible?

You can have single expect statement which loops, processing different outputs differently, something like:
expect {
"Enter file" {
send "./id_rsa\r"
exp_continue
}
"Enter passphrase" {
send "\r"
exp_continue
}
"Enter same passphrase" {
send "\r"
exp_continue
}
$
}
But note that you need some way to break out of the loop. The last pattern here - $ has no exp_continue (or any other action) so it will break out of the loop if the prompt you get when logged in includes $.
See the full documentation at https://www.tcl.tk/man/expect5.31/expect.1.html

Expect in any order, with infinite timeout:
#!/usr/bin/expect
spawn /path/to/some-programme
expect {
"some string" {
set timeout -1
send "some input\r"
exp_continue
}
"some other string" {
set timeout -1
send "some other input\r"
exp_continue
}
...
}
#no 'interact'
#end of file

Related

Expect Script for SFTP not working in bash

I have created a shell script containing an expect script for getting a file from remote location. Everything works well, until some command is sent. Whether it is 'ls' or 'pwd' or any other command, the expect script ends abruptly. Could you guy help me out with this.
NOTE : Security is not a concern, hence not using Public keys.
#!/bin/ksh
FTPREMOTEPATH=/Inbox
FTPREMOTEFILENAME=test.CSV
/usr/bin/expect -f - <<EOFEXPECT1
set timeout 60
spawn sftp -oPort=1002 username#test.server.com
expect {
default { exit 1}
-re "failed|invalid password|Permission denied" {exit 2}
"Connection closed" {exit 1}
timeout {exit 1}
}
expect "Password:"
send "password\r"
expect {
default {exit 1}
-re "password|Enter password for " {puts "Incorrect Password"; exit 2}
"sftp>" {send "cd $FTPREMOTEPATH \r"}
}
expect "sftp>"
send "pwd\r"
send "get $FTPREMOTEFILENAME \r";
EOFEXPECT1
In above script, the scripts end abruptly after sending cd $FTPREMOTEPATH.
Below is the Output :
$ ./test.sh
spawn sftp -oPort=1002 username#test.server.com
Enter password for username
Password:
sftp> cd /Inbox
sftp> $
Imagine you call a local restaurant, and say "get me some food" and then just hang up. What is the restaurant supposed to do?
When you wait for the sftp prompt, you're waiting for the food to be delivered.
I'd recommend 2 things:
before send "get ... change the timeout value to -1 -- that will help if it takes longer than 60 seconds to receive the file.
after you send "bye", expect eof -- that lets the sftp connection close gracefully.
The reason is that your Expect script has reached the end of the script, and therefore terminates the child (ftp) process before the ftp process has a chance to finish downloading the file.
Okay, Apparently i was missing expect "sftp>" {send "bye\n"} , before EOF (EOFEXPECT1).
However, i would still be interested in knowing the significance of bye in expect script.
Here is the updated and working code :
#!/bin/ksh
FTPREMOTEPATH=/Inbox
FTPREMOTEFILENAME=test.CSV
/usr/bin/expect -f - <<EOFEXPECT1
set timeout 60
spawn sftp -oPort=1002 username#test.server.com
expect {
default { exit 1}
-re "failed|invalid password|Permission denied" {exit 2}
"Connection closed" {exit 1}
timeout {exit 1}
}
expect "Password:"
send "password\r"
expect {
default {exit 1}
-re "password|Enter password for " {puts "Incorrect Password"; exit 2}
"sftp>" {send "cd $FTPREMOTEPATH \r"}
}
expect "sftp>"
send "pwd\r"
send "get $FTPREMOTEFILENAME \r";
expect "sftp>" {send "bye\n"}
EOFEXPECT1

How to have Expect script output to file

I need to gather some statistics from a network switch and would like to use an expect script to output the data to a file. I would like to run this script as a cron job and have the data appended to the file when it is run. Following is the working code I have so far, I just do not know how to get the output to a file. Any help is greatly appreciated.
#!/bin/bash
#get mac-address count
/usr/bin/expect -f -<<EOD
spawn ssh user#192.168.1.100
sleep 2
#Catch the password prompt and send supplied password
expect {
"*word:" {send "password\r"}
}
sleep 1
#Get into enabled mode
expect {
"*>" {send "system-view\r"}
}
sleep 1
expect {
"*]" {send "display mac-address count\r"}
}
sleep 1
expect {
"*]" {send "quit\r"}
}
sleep 1
expect {
"*>" {send "quit\r"}
}
sleep 1
expect eof
EOD

How to use a linux expect script to reconnect to forticlientvpn

I have this code that connects my network to an external vpn, but sometimes this connection is lost. I need my code to detect the error and try to connect again.
set force_conservative 0
if {$force_conservative} {
set send_slow {1 .1}
proc send {ignore arg} {
sleep .1
exp_send -s -- $arg
}
}
set timeout -1
spawn $env(SHELL)
match_max 100000
proc tryconnection {} {
send -- "./forticlientsslvpn_cli --server SERVER:PORT --vpnuser USER"
expect -exact "./forticlientsslvpn_cli --server SERVER:PORT --vpnuser USER"
send -- "\r"
expect -exact "\r\nPassword for VPN:"
send -- "PASSWORD\r"
expect -exact "\r\nSTATUS::Setting up the tunnel\r\nSTATUS::Connecting...\r"
send -- "Y\r"
expect -exact "\r\nSSLVPN down unexpectedly with error:6\r" {
puts "Send Ctrl+C"
send \003
tryconnection
}
expect eof
}
tryconnection
I would remove the -exact option:
expect "*SSLVPN down unexpectedly with error:6*" { ...
Try running with expect -d to see why your pattern is not matching when you lose connection.

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