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

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)

Related

Cannot use echo command if i import except in script

I want to use expect method in my script so i used this command in script
#!/usr/bin/expect
now I'm unable to declare any array or even use echo commands after using this command
Full Code
#!/usr/bin/expect
declare -a arr=("ip1" "ip2")
for i in "${arr[#]}"
do
spawn ssh myhost#i
send "exit\n"
done
The entire array declaration and for loop throws error like invalid command name , i'm not able to use echo command . when i try the echo command without importing except everything works as expected .
The first line declare that your script is an expect script and expect script uses Tcl scripting language. What you did was mixing bash and Tcl in the same script, which is why the error message. Setting up an list in Tcl and loop over is easy:
#!/usr/bin/expect
set arr {ip1 ip2}
foreach i $arr {
spawn ssh myhost#$i
send "exit\r"
}

How to use telnet in a script?

I need to connect in a system where I have to SSH first then telnet. Then I can start executing some command.
I am struggling about the telnet part. Can you tell me how I can make it please? Is there another alternative than spawn please? Thank you
#!/bin/bash
cat command.sh | sshpass -p 'passowrd' ssh -q -o StrictHostKeyChecking=no root#pc1;
Then my command.sh
#!/bin/bash
spawn telnet pc_modem
expect "login:"
send "root"
expect "Password:"
send "youyou"
cliclient GetMonitoringData;
In this use, the shebang has no effect*. You're passing the contents of the script file to ssh to be executed line by line as if each line were separate commands that will be interpreted by the shell instead of expect.
Try changing command.sh to something like:
# no shebang here
/bin/expect -f - <<<'spawn telnet pc_modem
expect "login:"
send "root"
expect "Password:"
send "youyou"
cliclient GetMonitoringData;'
This sends the expect script as a here string to expect's STDIN. If you use variables in your expect script you may need to change the quoting or escaping depending on whether they are shell or TCL variables and where the substitution needs to take place.
* The shebang is used by the kernel to select the program to interpret the contents of the file when the file has been marked as executable and is run by invoking the file by its name. When a file is run by explicitly naming the interpreter (e.g. sh run_me or ssh user#host run_me_there) the shebang doesn't come in to play.
I find out the answer and works perfectly :
/bin/expect <<<'spawn telnet pc_modem
expect "login:"
send "root\r"
expect "Password: ";
send "youyou\r"
send "yourcommand1\r"
send "yourcommand2\r"
expect eof
'

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.

How to use Bash script variables in Expect conditional statements

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.

Problems with terminating connection after running scripts on remote computer using shell script

This is the first time I am writing a shell script. I tried to do as much research as I can to avoid dumb/repetitive question. Please excuse if its repeat/dumb question.
I have a shell script which connects to remote linux machine and runs scripts there. I am using 'expect' to spawn a ssh connection and to issue commands to trigger the job. However, I am having issues while closing the connection after completing the job.
This is my script:
set prompt "(%|#|\\$|%\]) $"
expect -c 'spawn ssh $UN#$STAGE ;
expect password ; send "$PASS \n";
expect -regexp "$PROMPT"; send "./settings.$UN.sh > settings_log.txt \n";
interact'
This script successfully runs the script file for me ($UN and $STAGE parameters are input to the script. I omitted that here for simplicity). However, this leaves me with an open connection.
I tried to close the connection after running the script by using following instead of above
expect -c 'spawn ssh $UN#$STAGE ;
expect password ; send "$PASS \n";
expect -regexp "$PROMPT"; send "./settings.$UN.sh > settings_log.txt \n";
expect -regexp "$PROMPT"; send "exit \n"'
This does close the connection but I noticed that my script file did not run at all. Also the settings_log.txt is not generated at all.
Does this mean, that exit command is aborting the process before its completion? I tried using 'sleep' before exit but it did not help. Is there a better suggested way to terminate the connection when using expect?
Any help is appreciated.
with expect, you terminate your send commands with \r not \n, so
expect -c 'spawn ssh $UN#$STAGE
expect password
send "$PASS\r"
expect -regexp "$PROMPT"
send "./settings.$UN.sh > settings_log.txt\r"
expect -regexp "$PROMPT"
send "exit\r"
expect eof'
Note you can execute remote shell commands and copy files using ssh and scp, directly, without using expect.
For example,
scp ./settings.$UN.sh $UN#$STAGE:settings_log.txt
ssh $UN#$STAGE whatever-you-need-to-execute
The connection will close as soon as soon as whatever-you-need-to-execute completes.
Your outer script seems to be written in csh and sets a variable named "prompt", but your expect script is using a variable called "PROMPT". Try making the two variable names match case.

Resources