Why does my Expect script only echo the command not running? - bash

I'm trying to automate some ssh process. I have my Expect code. But my Expect code only echos/prints out the command. It doesn't actually run the command.
#!/usr/bin/expect -f
set timeout 10
set usrnm "aaaaaa"
set pwd "pppppp"
set addr1 "xxx.cloud.xxx.com -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
set addr2 "xxx.xxxx.xxxx.com"
spawn ssh $usrnm#$addr1
expect {
"(yes/no)?" {send "yes\r";exp_continue}
"password: " {send "$pwd\r"}
}
expect "*#"
send "ssh $usrnm#$addr2\r"
expect {
"(yes/no)?" {send "yes\r";exp_continue}
"password:" {send "$pwd\r"}
}
expect "*#"
send "cd /tmp/myself/folder\r"
expect "*#"
send "./run_engine.sh test.py\r"
expect eof
#interact
So if I do
expect my_expect.exp
it just prints the command:
spawn ssh aaaaaa#xxx.cloud.xxx.com -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
(10s later)
ssh aaaaa#xxx.xxxx.xxxx.com
(10s later)
cd /tmp/amz337/COAFALV
(10s later)
./run_engine.sh test.py
(exit)
What's wrong with my script?

Because Tcl (and thus Expect) does not change the word boundaries when variables get substituted. You are trying to log into the host named exactly:
xxx.cloud.xxx.com -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
spaces and all.
Logically, it does not make sense to put ssh options into a variable that holds the address. May I suggest:
set addr1 "xxx.cloud.xxx.com"
set addr2 "xxx.xxxx.xxxx.com"
set ssh_opts($addr1) {-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no}
set ssh_opts($addr2) {}
Then
spawn ssh {*}$ssh_opts($addr1) $usrnm#$addr1
The {*} syntax is Tcl's "splat" operator that splits a word with spaces into the individual words. See https://tcl.tk/man/tcl8.6/TclCmd/Tcl.htm rule #5.
Later, when you connect to the second machine, you're interpolating into a string, so the splat is not necessary:
send "ssh $ssh_opts($addr2) $usrnm#$addr2\r"
You might want to catch timeout events and abort the script:
expect {
timeout {error "timed-out connecting to $addr1"}
"(yes/no)?" {send "yes\r"; exp_continue}
"password: " {send "$pwd\r"}
}
At the end of your script, after the run_engine script completes, you're still connected to addr2, so expect eof will not actually detect EOF on the spawned process. You'll timeout after 10 seconds and the Expect process will exit. For tidiness, you should:
send "./run_engine.sh test.py\r"
expect "*#"
send "exit\r"
# This prompt is from addr1
expect "*#"
send "exit\r"
# _Now_ the spawned ssh process will end
expect eof
If you think the run_engine script will take longer than 10 seconds, you should adjust the timeout variable before sending that command.
Also, while developing an Expect script, you should turn on debugging:
exp_internal 1
That will show you what's going on behind the scenes, especially when it comes to seeing if your patterns are matching.

Related

Running multiple expect/TCL code inside same shell script

I have to run multiple chunks of expect script inside the shell script. In between each expect scripts, I have to run shell code. Below is conceptual non-working code where, 1st chunk would SSH into a remote node, and the subsequent chunks will run some other commands on the same remote server.
#!/bin/bash
#first chunk of expect code
/usr/bin/expect - <<EOF
global spawn_id
spawn /bin/bash
expect -re ".*\$"
spawn -- "ssh -oStrictHostKeyChecking=no -oCheckHostIP=no admin#localstack1\r"
expect -re ".*assword.*"
send 'admin\r'
expect -re ".*\$"
interact
EOF
#running shell commands
ls -lrt
#2nd chunk of expect code
/usr/bin/expect - <<EOF
expect -re ".*\$"
send -- "hostname\r"
expect -re ".*\$"
EOF
This seems to be impossible, However would like to know more from experts. unlike pexepct, here I am unable to find any way to reuse the spawned shell(passing the child object).
why?
from another program I am getting the list of commands,expected_results and timeout , so I cannot hardcode them within the expect script. Sometime there are 1 set of commands,sometimes multiple set of commands.
Here's an example of writing the whole thing in Expect/Tcl as suggested in the comments:
#!/usr/bin/expect
#spawn /bin/bash
#expect -re ".*\$"
spawn -- "ssh -oStrictHostKeyChecking=no -oCheckHostIP=no admin#localstack1\r"
expect -re ".*assword.*"
send 'admin\r'
expect -re ".*\$"
interact
#running shell commands
puts [exec ls -lrt]
expect -re ".*\$"
send -- "hostname\r"
expect -re ".*\$"
I've commented out the spawn of bash because that appears redundant.

Loop through file and send commands over expect

I am trying to send commands from a file to a device over expect. I tried sending them one at a time from my local machine but all of my file paths were relative to local, not relative to the remote device. My solution was to try to upload the file to the device and load the commands from there. When I try to load the file I keep getting a permissions issue even though if I cat the file from the device I don't have a problem reading it. The file has 1 command per line.
devicepath=rsync://root#localhost:$PORT_RSYNC/root/var/root/file.txt
/usr/bin/rsync -Pavr $1 $devicepath
expect <<- expect_feed
set send_slow {1 .001}
spawn ssh -o NoHostAuthenticationForLocalhost=yes -p $PORT_SSH root#localhost
expect -re "password:"
send -s "password\r"
expect -re $PROMPT_ROOT
send -s "chmod 777 /var/root/file.txt\r"
expect -re $PROMPT_ROOT
set f [cat /var/root/file.txt]
set cmds [split [read $f] "\n"]
close $f
foreach line $cmds {
send -s "$line\r"
expect -re $PROMPT_ROOT
expect_feed
This yields:
root# cat: /var/root/file.txt: Permission denied
I also tried
set f [open /var/root/file.txt]
...but it gave the same error.
If the file you send over contains shell commands, treat it as such and simply source it on the remote host
devicepath=rsync://root#localhost:$PORT_RSYNC/root/var/root/file.txt
/usr/bin/rsync -Pavr "$1" "$devicepath"
export PROMPT_ROOT PORT_SSH
expect << 'EXPECT_FEED'
set send_slow {1 .001}
spawn ssh -o NoHostAuthenticationForLocalhost=yes -p $env(PORT_SSH) root#localhost
expect -re "password:"
send -s "password\r"
expect -re $env(PROMPT_ROOT)
send -s ". /var/root/file.txt\r" ;# <<<<
expect -re $env(PROMPT_ROOT)
send "exit\r"
expect eof
EXPECT_FEED
I prefer to use quoted heredocs: shell variables can be passed to expect via the environment.
I'm assuming root's shell is a POSIX-type shell where . is the "source" command.
Thanks for the great suggestions. It is working like this.
It would probably work just as well without send slow now that it is sending one line at a time and waiting for a response.
The last command in the file is 'quit' in order to return to the root prompt, I suppose that could have been hard coded
cmdpath=$1
export cmdpath
expect << 'EXPECT_FEED'
set send_slow {1 .001}
set commandfile [open $env(cmdpath)]
spawn ssh -o NoHostAuthenticationForLocalhost=yes -p $env(PORT_SSH) root#localhost
expect -re "password:"
send -s "password\r"
expect -re $env(PROMPT_ROOT)
send -s ">the name of the process accepting commands<\r"
while { [gets $commandfile line] != -1 } {
expect -re $env(PROMPT)
send -s "$line\r" }
expect -re $env(PROMPT_ROOT)
send "exit\r"
expect eof
EXPECT_FEED

How to use expect in Shell script like nested If Else loop?

I have written below Shell script which is intended to get Model name from remote host by doing SSH and executing the command.
#!/bin/bash
> output.csv
IFS=","
echo "IP,Model Name" >> output.csv
while read ip
do
#echo "Current IP is: $ip"
model=expect -c 'spawn ssh username#'"$ip"' "show version | in cisco"; expect -re "The.*(yes/no)?"; send "yes\r"; expect -re ".*UNAUTH.*password:"; send "password\r";' | grep cisco
echo "$ip,$model" >> output.csv
done < Check_SSH.csv
When I execute below command manually, then it gives expected model name as output.
Command:
expect -c 'spawn ssh username#'"$ip"' "show version | in cisco"; expect -re "The.*(yes/no)?"; send "yes\r"; expect -re ".*UNAUTH.*password:"; send "password\r";' | grep cisco
But when its put into script like above it doesn't produce any output.
Also, there are MOTD (Message of the day) configured on most of the servers and "The authenticity of host..." message to adding server into .ssh/known_hosts, So I tried to handle them in script but Expect is failing to handle the situation when MOTD doesn't appear or when remote is already present in .ssh/known_hosts.
Any help is highly appreciated to get this script running.
Expected output:
IP,Model Name
8.8.8.8,C9407R
8.8.8.1,C9407R
8.8.8.2,C9407R
8.8.8.3,C9407R
First, you're missing the Command Substitution syntax to execute the expect code:
model=$(expect -c ...)
# ....^^.............^
Next, to optionally expect patterns, you need the expect {patt1 action1 patt2 action2 ...} form:
expect -c '
spawn ssh username#'"$ip"' "show version | in cisco"
expect {
-re "The.*(yes/no)?" {send "yes\r"; exp_continue}
-re ".*UNAUTH.*password:" {send "password\r"; exp_continue}
eof
}
'
That way, expect can match any of the patterns. The exp_continue command "loops" within the same expect command so you can match more than one of them. The eof pattern matches when ssh connection closes after the "show version ..." command has finished.
Newlines for readability.
Putting this together:
model=$(
expect -c '
spawn ssh username#'"$ip"' "show version | in cisco"
expect {
-re "The.*(yes/no)?" {send "yes\r"; exp_continue}
-re ".*UNAUTH.*password:" {send "password\r"; exp_continue}
eof
}
' | grep -i cisco
)
I have a feeling that there's more you need to do in the grep part, but you didn't show the output of just the expect command.
update:
use spawn -noecho ssh ... so expect will not print the spawn command.
then, you'll get whatever output ssh needs to show for the login process, and then the "show" command output:
if you're expecting exactly 1 line of output, you might want to change grep to tail -n 1.
otherwise, show the output you get and we can help you filter out the noise.
update 2: filtering out the noise
I'm going to assume that the regex pattern cisco (.*) processor is what you need to match:
model=$(
expect -c '
log_user 0
spawn ssh username#'"$ip"' "show version | in cisco"
expect {
-re "The.*(yes/no)?" {send "yes\r"; exp_continue}
-re ".*UNAUTH.*password:" {send "password\r"; exp_continue}
-re "cisco (.*) processor" {puts $expect_out(1,string)}
}
expect eof
'
)
log_user 0 turns off the spawned process's ability to write to stdout. Expect can still capture its output though.

perl program not running thorough automated script until it is executed manually first

i am writing code to automate some steps . First it is required to switch user and then run a perl script. Here is my code
if [ -a /try/Test ]
then
su trial -c ". /try/.profile Test"
expect -c 'spawn try1;
send "3\r";
send "1\r";
send "show\r";
interact';
fi
try1 is my perl program which i am trying to call.This script throws this error
couldn't execute "try1": no such file or directory
while executing
"spawn try1"
but once i do this step manually and then run this script then this script runs without nay error.
I think you've already asked about it (and I did answer, didn't I)?
Here's the basic skeleton (make sure to add error/timeout/unexpected output handling):
# collect password
stty -echo
send_user -- "Password: "
expect_user -re "(.*)\n"
send_user "\n"
stty echo
set pass $expect_out(1,string)
spawn sudo sh;
expect -re ": *$";
send -- "$pass\r"
expect -re "\$ *$";
send "echo SETTING PARAMS\r";
expect -re "\$ *$";
send "echo RUNNING MY COMMAND\r";
expect -re "\$ *$";
interact

expect in bash script × 16172

I try to set a expect script in bash.
#!/bin/bash
/usr/bin/expect <<- EOD
set router 192.168.0.251
set user admin
set pass test
set timeout 1000
set filesave [exec date +%m-%d-%Y]
spawn telnet $router
send "\n"
expect "Username:"
send "$user\n"
expect "Password:"
send "$pass\n"
expect ">"
send "en\n"
expect "Password:"
send "$pass\n"
send "term len 0\n"
log_file $router--$filesave.cfg
send "show running-config\n"
expect "end\r"
send "\n"
log_file
send "exit\n"
EOD
cat /Users/test/Desktop/python/$router--$filesave.cfg | grep end
exit 0
I just got this output
./script2
spawn telnet
telnet> telnet>
Try changing your shebang from:
#!/bin/bash
to
#!/bin/expect
and remove:
/usr/bin/expect <<- EOD
And see if that works.
Update: if you need to have expect run as part of your bash script, either encapsulate the expect code in a separate script with a expect shebang and source it from your bash script, or encode it as in the following example:
expect_sh=$(expect -c "
spawn ssh $login#$IP
expect \"password:\"
send \"$password\r\"
expect \"#\"
send \"cd $dest_dir\r\"
expect \"#\"
send \"chmod +x $server_side_script $other_script\r\"
expect \"#\"
send \"./$device_side_script\r\"
expect \"#\"
send \"cat $deploy_count\r\"
expect \"#\"
send \"exit\r\"
")
echo "$expect_sh"
The problem is that bash is interpreting all the $variables before expect sees the script. Thus you're simply spawning telnet with no hostname given. Change:
/usr/bin/expect <<- EOD
to:
/usr/bin/expect <<- 'EOD'
This has the effect of single-quoting the entire here-document.
http://www.gnu.org/software/bash/manual/bashref.html#Here-Documents
Note also that the next bash command (cat /Users/...) relies on variables defined in Expect -- they are not defined in bash. Try this
#!/bin/bash
export router=192.168.0.251
export filesave=$(date +%m-%d-%Y)
/usr/bin/expect <<- 'EOD'
set router $env(router)
set filesave $env(filesave)
# the rest stays the same

Resources