How to use Bash script variables in Expect conditional statements - bash

I am writing a Bash script and using Expect to do sftp. Now in the Expect block I want to access a Bash variable in a conditional statement. But, I am unable to do so. How can do this?
Also, the execution of this script is controlled from a C program and I want redirect the output to a log file (which again is dynamic). Can I do that and suppress all the output on standard output.
Here is the code:
!/usr/bin/bash
host=$1
user=$2
pass=$3
action=$4
path=$5
echo "Starting...."
function doAction {
strAction="\""$action"\""
echo $strAction
/usr/bin/expect <<EOF > logfile.txt
**set bashaction $strAction**
spawn sftp $user#$host
expect "password:"
send "$pass\r"
expect"sftp>"
send "cd $path\r"
**if {$bashaction == "TEST"} {**
expect "sftp>"
send "prompt\r"
}
expect "sftp>"
send <sftp command>
expect "sftp>"
send_user "quit\n"
exit
EOF
}
doAction
echo "DONE....."
For 1. using an Expect script instead worked.
For the logging issue, using log_user 0 and log_file -a <file> helped.

You don't need to use Bash. Expect can handle all that:
#!/usr/bin/expect
set host [lindex $argv 0]
set user [lindex $argv 1]
set pass [lindex $argv 2]
set action [lindex $argv 3]
set path [lindex $argv 4]
puts "Starting...."
puts "\"$action\""
spawn sftp $user#$host
expect "password:"
send "$pass\r"
expect"sftp>"
send "cd $path\r"
if {$action == "TEST"} {
# Do something
} else {
# Do something else
}
expect "sftp>"
send_user "quit\r"
puts "DONE....."
Coming from Bash, the Tcl/Expect syntax is a little strange, but you should not have any problem expanding the above skeleton.

Accessing Environment Variables from TCL and Expect
Since you are calling this Expect script from another process, you can make use of environment variables. For example, if your parent process has exported action to the environment, then you can access its value within your expect script with:
$::env(action)
In Bash, you can mark the variable for export with the export builtin. For example:
export action
Since I'm not sure how you're invoking the Expect script from C, it's up to you to make sure the variable is properly exported.
Disable Logging to Standard Output
To disable logging to standard output from spawned processes, Expect provides the log_user command. You can prevent your spawned processes from writing to stdout with log_user 0.
The expect(1) manual says:
By default, the send/expect dialogue is logged to stdout (and a
logfile if open). The logging to stdout is disabled by the command
"log_user 0" and reenabled by "log_user 1". Logging to the logfile
is unchanged.
This doesn't actually close standard output, which is generally not what you want anyway. Doing so will cause anything that writes to stdout to throw an error like this:
can not find channel named "stdout"
while executing
"puts hello"
(file "/tmp/foo" line 8)

To suppress output to the standard output you can use
command here >/dev/null 2>/dev/null
To write to a log file, you can use similar piping (> or >>), or the tee command if you want to write the output in the middle of a long pipe.

Related

Need modification in an ssh script that's written in expect

Am working on a script to ssh into list of servers using expect tool. Getting below error while running it
./script
#!/usr/local/bin/expect -f
while /usr/bin/read hostname
do
spawn ssh user#$hostname
expect "user#$hostname's password"
send "resuidt\n"
expect "user#$hostname"
interact
done < srvlist
Below is my error:
missing operand at _#_
in expression "_#_/usr/bin/read"
(parsing expression "/usr/bin/read")
invoked from within
"while /usr/bin/read hostname"
(file "./script" line 3)
Need help to fix this error.
You are writing an Expect program, which is basically a Tcl program. Your while loop is not Tcl syntax, but looks like a (Posix/Ksh/Bash/Zsh)-shell script.
You have to make up your mind: Write everything in Tcl, or split your application into two files: One (in shell script) as "main program", and a separate expect script, which will be called by the shell script.
As user1934428 indicates you are using bash-type while loop syntax.
Below is one example of how to make an expect script perform the actions you want.
#!/usr/local/bin/expect -f
set file hostname
set user myusername
set passwd mypassword
set f [open $file]
foreach target [split [read $f] "\n"] {
spawn ssh $user#$target
expect {
timeout {send_user "Expect Timeout\n" ; exit}
"password:"
}
send "$passwd\r"
expect {
timeout {send_user "Expect Timeout\n" ; exit}
"$user#$target"
}
interact
}
close $f
I included timeouts in the expect sections because I've found if you do not add these safety mechanisms the expect script can proceed even without the proper responses.
if you want to use shell variables directly into the expect script then you have to pass those variables as $env(shell_variable_name) inside the expect script
example:spawn ssh $env(myusername)#$env(hostname)

Expect script failing to continue

I have an expect script which is logging onto a device and sending a command, however the script completes without finishing, its like the last expect isn't being picked up.
How do I enable debugging to watch the script progress so I can identify where the issues is?
My script looks like this...
#!/usr/bin/expect
set hostname [lindex $argv 0]
set username "a.user"
set password "a.pass"
spawn telnet $hostname
expect "Username:" {
send "$username\r"
expect "Password:"
send "$password\r"
}
expect "#" {
send "sh ver\r"
}
It stops processing at the below line;
expect "#" {
however I can clearly see the # in the last output.
Thanks

How to pass the argument in expect shell script during runtime

I am trying to pass the password dynamically, while running the expect script.
Script looks somewhat like this :
#!/usr/bin/Expect
set server [lindex $argv 0]
send "enter you password"
read Password;
send $password\n;
spawn ssh c1210427#$server ...
Got stuck while getting the password from terminal during the running script.
The [read] command reads until end of file so it's waiting for you to close the terminal. Use the [gets] command instead:
set password [gets stdin]
Also, you're using [read] wrong. The first argument is the channel id to read from. See the documentation for more info:
http://www.tcl.tk/man/tcl8.6/TclCmd/read.htm
http://www.tcl.tk/man/tcl8.6/TclCmd/gets.htm
In your code, you have used the following code like a puts statement
send "enter your password"
which is not a proper way. Usually, send command will try to send commands to the console and if any process spawned via script, then this command will be sent to that process.
Anyway, you will get the statements get printed in the console. But, be aware of it. Instead, better use send_user command.
You can try out this
#!/usr/bin/expect
set server [lindex $argv 0]
stty -echo; #Disable echo. To avoid the password to get printed in the terminal
send_user "enter you password : "
# Using regex to grab all the input till user press 'Enter'
# Each submatch will be saved in the the expect_out buffer with the index of 'n,string'
# for the 'n'th submatch string
# expect_out(0,string) will have the whole expect match string including the newline
# The first submatch is nothing but the whole text without newline
# which is saved in the variable 'expect_out(1,string)
expect_user -re "(.*)\n" ;
stty echo; #Enable echo
set pwd $expect_out(1,string)
send $pwd\n;
expect "some-other-statment"
#Your further code here
You can remove the stty -echo and stty echo if you don't bother about the password getting printed in console
Reference : http://www.tcl.tk/man/expect5.31/expect.1.html

How to return a value from child expect script to parent sh script

I have a expect script inside a shell script. My problem is I am unable to get a variable value from the child expect script to the shell parent script.
Please find my code below:
#!/bin/sh
expect <<- DONE
spawn telnet myemailserver.com imap
expect "* OK The Microsoft Exchange IMAP4 service is ready."
send "a1 LOGIN myuser mypass\r"
expect "a1 OK LOGIN completed."
send "a2 EXAMINE INBOX\r"
expect "a2 OK EXAMINE completed."
send "a3 SEARCH UNSEEN\r"
expect "a3 OK SEARCH completed."
set results $expect_out(buffer)
set list [split $results "\n"]
send "a4 LOGOUT\r"
expect "Connection closed by foreign host."
spawn echo $list
expect eof
DONE
echo $list
exit 0
I found out that the variable list at the last line is empty. Is there a way to pass the value from variable $list to the shell parent script?
Your here-document is subject to shell variable expansion before the script is given to the expect interpreter. The $list variable is substituted with nothing (assuming you don't already have a shell variable named list in your program). You need to ensure the here-doc is single quoted (shown below)
Just like working with awk or sed, the shell inter-process communication is performed through passing data along the standard IO channels: the shell script has to capture the output of the expect program:
list=$( expect <<'END'
log_user 0
# expect program here
puts $list
END
)
echo $list
Since I'm suppressing normal terminal output of spawned programs with log_user 0 in order to send only the crucial information back to the shell, you have to replace spawn echo with expect's puts command.

Expect Script Terminating on its own

I have written the below expect script but it's not working as expected. I want the script to terminate automatically when all the commands are executed. However , the script either never terminates ( if set timeout -1 is used ) or terminates within seconds even before my commands are executed. Can someone please help ?
Here's the script :
#!/usr/local/bin/expect
spawn su vserve
set password vserve
set PWD whoami
set cmdstr(0) "bash /apps/vpn/vserve/vserve_profile"
set cmdstr(1) "bash /apps/vpn/asap/scripts/change_loopback.sh"
set timeout -1
expect "*Password:*" {
sleep 1
send "$password\r"
send "$PWD\r"
sleep 1
for {set i 0} {$i<[array size cmdstr]} {incr i} {
send "$cmdstr($i)\r"
}
send \"exit\r\"
expect eof
}
Usually in an interactive shell, you have to expect the specific shell prompt before you send next command. That's the way we make sure the previous command has really finished.

Resources