Expect within a Bash script - bash

I wrote a Bash script with Expect within, to connect to a terminal server and clear lines.
I am unable to figure out the error I am getting, as in I have given all the braces necessary. I also do not understanding the couldn't read file "line": no such file or directory error.
How can I fix this?
My script:
#!/bin/bash
VAR=$(expect -c "
spawn telnet 1.1.1.1
expect {
"Password:" { send "password\r" ; exp_continue}
"Prompt>" { send "en\r" ; exp_continue}
"Password:" { send "password\r" ; exp_continue}
"Prompt#" {send "clea line 10\r" ; exp_continue}
"[confirm]" {send "Y\r" ; exp_continue}
"Prompt#" {send "clea line 11\r" ; exp_continue}
"[confirm]" {send "Y\r" ; exp_continue}
"Prompt#" {send "exit\r" }
}
")
echo $VAR
Its output:
missing close-brace
while executing
"expect"
couldn't read file "line": no such file or directory

The first problem is that the shell does not interpret nested double quotes as you might like. The easiest way to fix this is to put the Expect program in single quotes. This will be sufficient as long as there are no single quotes in the Expect program itself.
The next problem you will run into is that having all the patterns and actions in a single expect command will process them in parallel. What is actually happens is that the first Password: pattern will match any time it sees that string (i.e. even for the admin password the second time around). This will be a problem if the two passwords need to be different. At a minimum, identical patterns will need to go into separate expect commands so that they can be executed sequentially. This problem also affects the Prompt# pattern where you look for it three times and want to send three different responses.
Later, you will get an error after you send the first clear command. Expect interprets square brackets inside double quotes in a way that is similar to how shells interpret $() or `` (i.e. command substitution). You will see an error like this:
invalid command name "confirm"
while executing
"confirm"
invoked from within
"expect {
⋮
It is trying to run confirm as a Tcl (or Expect) command. You can use curly brackets ({}) to prevent Tcl from making this interpretation. Furthermore, expect patterns are treated as “glob” expressions by default (i.e. like shell wildcards), so even if you write {[confirm]} as the pattern, it will still not be used for an exact string match (it would match any single character c, o, n, f, i, r, or m). You must use the -ex flag to mark the pattern for exact matching.
Fix these issues, drop some of the unnecessary quoting, and you might end up with something like this:
#!/bin/sh
VAR=$(expect -c '
proc abort {} {
puts "Timeout or EOF\n"
exit 1
}
spawn telnet 1.1.1.1
expect {
Password: { send "password1\r" }
default abort
}
expect {
Prompt> { send "en\r"; exp_continue }
Password: { send "password2\r" }
default abort
}
expect {
Prompt# { send "clea line 10\r"; exp_continue }
-ex {[confirm]} { send "Y\r" }
default abort
}
expect {
Prompt# { send "clea line 11\r"; exp_continue }
-ex {[confirm]} { send "Y\r" }
default abort
}
expect {
Prompt# { send "exit\r"; exp_continue }
timeout abort
eof
}
puts "Finished OK\n"
')
echo "$VAR"

The problem is that that the double quotes in the Expect script are treated as the end of the Expect script. Try mixing single quotes with double quotes:
#!/bin/bash
VAR=$(expect -c '
spawn telnet 1.1.1.1
expect {
"Password:" { send "password\r" ; exp_continue}
"Prompt>" { send "en\r" ; exp_continue}
"Password:" { send "password\r" ; exp_continue}
"Prompt#" {send "clea line 10\r" ; exp_continue}
"[confirm]" {send "Y\r" ; exp_continue}
"Prompt#" {send "clea line 11\r" ; exp_continue}
"[confirm]" {send "Y\r" ; exp_continue}
"Prompt#" {send "exit\r" }
}
')

#Chris: I incorporated the changes you suggested and my code is working now.
However, I had to make two more changes stated below:
The single quote which you mentioned prevents parameter substitution. For example, I cannot write $IP in place of 1.1.1.1. Hence, to get around this, I removed the single quotes and replaced with double quotes. As you mentioned nested doubles quotes are not interpreted by Bash which is true. Hence I rewrote the inside double quotes as
send \"password1\r\"
That is adding backslashes before the double quotes inside. This corrects the problem of parameter substitution.
Even after I put two/three actions within a single expect command, as they run in parallel I still faced issues. So taking your suggestion, I put each of the action in a separate Expect command. Something like:
expect {
Prompt> { send "en\r"; exp_continue }
}
expect {
Password: { send "password2\r" }
}

Related

Writing Expect Script for random questions in while loop

I am trying to write an expect script with a while loop where I have a set of questions that will be asked randomly again and again. I have created a script for the same but that is not working as expected. Is it important that all the expectations in the expect block should be in the sequence? Also, Is it the correct way of exiting the while loop in case of success?
#!/usr/bin/expect -f
set timeout 1
spawn ./AnotherFile.sh
while {1} {
expect {
{Enter password:} {send -- "Password\r";exp_continue}
{Trust this certificate? [no]:} {send -- "yes\r";exp_continue}
{Enter pass phrase:} {send -- "Password\r";exp_continue}
{Verifying - Enter pass phrase:} {send -- "Password\r";exp_continue}
{Country Name (2 letter code) [AU]:} {send -- "IN\r";exp_continue}
{State or Province Name (full name) [Some-State]:} {send -- "XX\r";exp_continue}
{Locality Name (eg, city) []:} {send -- "XXXXX\r";exp_continue}
{Organization Name (eg, company) [Internet Widgits Pty Ltd]:} {send -- "XXXXX\r";exp_continue}
{Organizational Unit Name (eg, section) []:} {send -- "XXXXX\r";exp_continue}
{Common Name (e.g. server FQDN or YOUR name) []:} {send -- "XXXXX\r";exp_continue}
{Email Address []:} {send -- "\r";exp_continue}
}
expect {
{"Successfully Done."} {send -- "exit\r"}
}
}
expect eof
close $spawn_id
You can also think of my problem in terms of "C" code as:
While(1)
{
Switch(expect_command)
{
case "Enter Password":
send "Password\r"
case "is certificate valid"
send "yes\r"
...
case "successfull"
send "exit\r"
}
}
Put "Successfully Done" in the same expect command as the others. There should be at least one pattern that will not exp_continue so the expect command can actually end (without timing out).
Also, since you're in a while 1 loop, you need to send "exit\r"; break

How to continue with next command if the remote switch is not responding in an expect script

I'm passing the switch name from a bash script. The bash script iterates through a text file and sends the switch name. Sometimes if the switch is not responding, the expect script gets stuck. I want the script to wait for some time and move to next switch or at least come out. I have used the timeout but with no help.
set timeout 60
.
.
.
expect "*> "
send "ssh -l admin $switchName.XXX.XXXXXX.net switchshow -portname > $filename1\r"
sleep 2
expect {
"*(yes/no)? " {
send "yes\r"
exp_continue
}
"*assword: " {
send "$password\r"
}
default {
exp_continue
}
}
Any suggestion is appreciated.
you want to expect to see the "timeout" keyword:
expect {
timeout {
puts "timed out waiting for $switchName"
}
"*(yes/no)? " {
send "yes\r"
exp_continue
}
"*assword: " {
send "$password\r"
exp_continue
}
eof
}
I also updated your conditions a bit so that you remain in the expect "loop" until the ssh command completes.

how to make command "ps" don't show password in expect script?

I have make an example as below. The password(mingps)is the shell variable. When execute the shell script, in the mean while, execute command "ps -ef", I found the result of "ps" showed the password(mingps). For security reason, I don't want to show the password when execute command "ps -ef". So how to hide it? Thanks in advance.
#!/bin/sh
MalbanIP="XXX.XXX.XXX.XXX"
MalbanLogin="ming"
MalbanPwd="mingps"
MalbanCmd="netstat"
firstTime="true"
/usr/bin/expect <<EOF
set timeout 10
log_user 0
spawn /usr/bin/ssh $MalbanIP -l $MalbanLogin
expect {
-nocase "continue connecting (yes/no)?" {
send "yes\r"
expect "password:" {
send "$MalbanPwd\r"; set firstTime "false"; exp_continue
}
}
"password" {
if {$firstTime == "true"} {
send "$MalbanPwd\r"; set firstTime "false"
} else {
log_user 1; puts stdout "password is wrong"; log_user 0;
exit 1
}
}
}
expect "0-0-3"
log_user 1
send "$MalbanCmd \r"
set results \$expect_out(buffer)
expect "0-0-3" { send "exit\r" }
expect eof
EOF
exit 0
Option 1
The best way is to switch to using RSA keys to log in, as this will enable you to significantly strengthen your overall system security substantially. With that, you can probably avoid using Expect entirely.
Option 2
However, if you can't do that, the key to fixing things is to not pass it as either an argument or an environment variable (since ps can see both with the right options). Instead, you pass the password by writing it into a file and giving the name of that file to the Expect script. The file needs to be in a directory that only the current user can read; chmod go-rx will help there.
MalbanPwdFile=/home/malban/.securedDirectory/examplefile.txt
# Put this just before the spawn
set f [open $MalbanPwdFile]
set MalbanPwd [gets $f]
close $f
You might also need to put a backslash in front of the use of $MalbanPwd so that it doesn't get substituted by the shell script part too early.
Option 3
Or you could stop using that shell wrapper and do everything directly in Tcl/Expect.
#!/usr/bin/expect
set MalbanIP "XXX.XXX.XXX.XXX"
set MalbanLogin "ming"
set MalbanPwd "mingps"
set MalbanCmd "netstat"
set firstTime true
set timeout 10
log_user 0
spawn /usr/bin/ssh $MalbanIP -l $MalbanLogin
expect {
-nocase "continue connecting (yes/no)?" {
send "yes\r"
expect "password:" {
send "$MalbanPwd\r"
set firstTime false
exp_continue
}
}
"password" {
if {$firstTime} {
send "$MalbanPwd\r"
set firstTime false
} else {
log_user 1
puts stdout "password is wrong"
log_user 0
exit 1
}
}
}
expect "0-0-3"
log_user 1
send "$MalbanCmd \r"
set results \$expect_out(buffer)
expect "0-0-3" { send "exit\r" }
expect eof
I suspect that this last option will work best for you in the longer term. It's definitely the simplest one (other than switching to RSA keys, which is what I've got deployed on my own infrastructure) and I think it is going to avoid some subtle bugs that you've got in your current code (due to substitution of variables at the wrong time).

expect script is not sending correct value

I wrote an expect script which will connect to a server and send a password depending on the prompt. The purpose is to update my password on new servers.
Basically, it will connect to a new server, expect to see (current) UNIX password: and then send my initial password. It will then see the 'New password:and 'Retype new password: prompts sending the new password each time.
My script loads these passwords from files into variables $pw1 and $pw2. I have evaluated them and verified that I have the correct values loaded into the variables. However, when I run the script, I am getting a token manipulation error on the initial password which tells me the value being sent is incorrect.
Perhaps my logic is incorrect?
EDIT: I have connected to one server that I'm running the script against and entered the old password exactly as it is in the file that the script loads into $pw2. It is working so I know that the password is not incorrect.
#! /usr/bin/expect --
#exp_internal 1
#set stty_init raw
set timeout 45
set prompt {\$ $}
set file1 [open [lindex $argv 0] r]
set pw1 [exec cat /home/user/bin/.pw1.txt]
set pw2 [exec cat /home/user/bin/.pw2.txt]
#puts $pw1
#puts $pw2
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho ssh -q $host
expect {
"*assword*" {
send -- "$pw1\r"
expect {
$prompt {
send -- exit\r
}
}
send -- exit\r
expect eof
}
-re $prompt {
send -- exit\r
expect eof
}
"(current)*" {
send -- "$pw2\r"
expect "New password"
send -- "$pw1\r"
expect "Retype new password"
send -- "$pw1\r"
puts \r
}
"continue*" {
send "yes\r"
expect {
"current" {
send -- "$pw2\r"
expect "New password"
send -- "$pw1\r"
expect "Retype new password"
send -- "$pw1\r"
puts \r
# expect eof
}
-re $prompt {
send -- exit\r
expect eof
}
}
}
}
}
puts \r
First, I'd like to note that you don't need to nest expect commands: look into the exp_continue command, instead. It will cut your code size in half simply by eliminating duplication.
Second, it looks your logic is, indeed, wrong. The first expect pattern is "*assword*". I would expect this to match when the remote server of your ssh is prompting for the user's current password, and it sends $pw1. However, everywhere else in your code, you seem to be treating $pw2 as the current password, and $pw1 as the new password.
Also, when you rewrite your code using exp_continue to eliminate the duplication and nesting, make sure you re-order your patterns so they're listed in order of most specific to least specific. Otherwise, you'll sometimes find the wrong pattern being matched, and the wrong code being executed.

Making decisions on expect return

I am trying to create an expect script which will send a different password string based on the "expect"
Condition A: If a cisco device has not been setup with a username then the first prompt will simply be "Password:" - then it should use passwordA (no username)
Condition B: If it has been setup with a username then the prompt will be "Username:" followed by "Password:" - then it should use Username and PasswordB
#!/bin/bash
# Declare host variable as the input variable
host=$1 
# Start the expect script
(expect -c "
set timeout 20
# Start the session with the input variable and the rest of the hostname
spawn telnet $host
set timeout 3
if {expect \"Password:\"} {
send \"PasswordA\"}
elseif { expect \"Username:\"}
send \"UsersName\r\"}
expect \"Password:\"
log_user 0
send \"PasswordB\r\"
log_user 1
expect \"*>\"
# send \"show version\r\"
# set results $expect_out(buffer)
#expect \"Password:\"
#send \"SomeEnablePassword\r\"
# Allow us to interact with the switch ourselves
# stop the expect script once the telnet session is closed
send \"quit\r\"
expect eof
")
You're doing it wrong. :)
The expect statement doesn't look to see what comes first, it waits until it sees what you ask for (and times out if it doesn't arrive in time), and then runs a command you pass to it. I think you can use it something like the way you're trying to, but it's not good.
expect can take a list of alternatives to look for, like a C switch statement, or a shell case statement, and that's what you need here.
I've not tested this, but what you want should look something like this:
expect {
-ex "Username:" {
send "UsersName\r"
expect -ex "Password:" {send "PasswordB\r"}
}
-ex "Password:" {send "PasswordA\r"}
}
In words, expect will look for either "Username:" or "Password:" (-ex means exact match, no regexp), whichever comes first, and run the command associated with it.
In response to the comments, I would try a second password like this (assuming that a successful login gives a '#' prompt):
expect {
-ex "Username:" {
send "UsersName\r"
expect -ex "Password:" {send "PasswordB\r"}
}
-ex "Password:" {
send "PasswordA1\r"
expect {
-ex "Password:" {send "PasswordA2\r"}
-ex "#" {}
}
}
}
You could do it without looking for the # prompt, but you'd have to rely on the second Password: expect timing-out, which isn't ideal.

Resources