smgopinath
smgopinath

Reputation: 31

while loop issues in expect script

I have written a expect script which works as follows:

  1. ssh to server1
  2. From server1 ssh to another server server2
  3. From server2 to server3 and then sudo into a user and run the commands.

In the script I read the hostnames and commands to be executed from two files called hostnames.out and commands.out. I used a while loop to iterate through each entries in hostnames.out and run the commands from the commands.out file.

I tested my script with single entry in the hostnames.out it works fine, but when i add multiple lines it is not running the commands on the hostnames from the second line onwards.

The format of the commands.out file is(one command per line):

ls -lrt
hostname
whoami

The format of the hostnames.out file is:

server1 user password server2 user password server3 user password

I have attached the script for reference. Please let me know where the problem is.

#!/usr/bin/expect
#####################################################
# script to automate manual works - remote 2        #
# Gopinath                                          #
#####################################################
#Variable declaration:

#Setting variable "prompt" for multiple prompts:
set prompt {[]$#%]\s*$}

#Reading commands list from file:

set fp1 [open "commands_list_2.out" "r"]
set file_data [read $fp1]
close $fp1


#  read the hosts file one line at a time
#  There should be no new line at the end of the hostnames.out file

     set fp [open "hostnames_2.out" "r"]

     while { [gets $fp data] >= 0 } {

          set ssh1 [lindex $data 0]
          set ssh1_usr [lindex $data 1]
          set ssh1_pwd [lindex $data 2]
      set ods [lindex $data 3]
      set ods_usr [lindex $data 4]
          set ods_pwd [lindex $data 5]
      set serv1 [lindex $data 6]
      set serv1_usr [lindex $data 7]
      set serv1_pwd  [lindex $data 8]

          puts $ssh1 
      puts $ssh1_usr
          puts $ssh1_pwd
      puts $ods
      puts $ods_usr
      puts $ods_pwd
      puts $serv1
      puts $serv1_usr
          puts $serv1_pwd

     spawn -noecho ssh $ssh1_usr@$ssh1
     expect { 

        "*password:" { send "$ssh1_pwd\r"}
        "*route*" { puts "login failed"; exit 1 }
        "timed out" { puts "login failed timed out"; exit 1 }
         }

      expect {
               -re $prompt { send "whoami\r"}
             }            


                  expect -re $prompt  { 

                            send  "ssh $ods_usr@$ods\r" }           
                  expect {
                     "password:" { send "$ods_pwd\r" }
                         }
                 }


           expect {
         -re $prompt { send "whoami\r"}
          }


                   expect -re $prompt  {

                            send  "ssh $serv1_usr@$serv1\r" }
                  expect {
                     "password:" { send "$serv1_pwd\r" }
                         }

                   expect -re $prompt 


         foreach a [list $file_data] {
         send "$a"
         expect -re prompt
         }
          expect -re prompt {   
              send "exit\r"
              }
     expect eof
     close $fp
`

Upvotes: 1

Views: 1046

Answers (1)

Donal Fellows
Donal Fellows

Reputation: 137567

So far as I can see, the main problem is that you've got this complicated chain of subprocesses on different hosts that isn't being cleaned up properly. It's compounded by your inconsistent indentation which is making your code harder to read.

The key thing is that you want your ssh sessions on server1 and server2 to just ssh to the next server in the chain, and the ssh session on server3 to just do a sudo. The easiest fix is to use exec ssh instead of ssh and exec sudo instead of sudo. Then, doing an exit at the innermost level will cause all those intermediate programs to also terminate, and your expect eof will trigger.

spawn -noecho ssh $ssh1_usr@$ssh1
expect {
    "*route*" { puts "login failed"; exit 1 }
    "timed out" { puts "login failed timed out"; exit 1 }
    "*password:" { send "$ssh1_pwd\r"; exp_continue }
     -re $prompt { send "whoami\r" }
}
expect -re $prompt  { 
    send "exec ssh $ods_usr@$ods\r"
}
expect {
    "password:" { send "$ods_pwd\r"; exp_continue }
    -re $prompt { send "whoami\r" }
}
expect -re $prompt  {
    send "exec ssh $serv1_usr@$serv1\r"
}
expect {
    "password:" { send "$serv1_pwd\r"; exp_continue }
    -re $prompt { send "whoami\r" }
}

# You seem to be missing the sudo here?

foreach a [split $file_data "\n"] {
    expect -re $prompt
    send "$a"
}

expect -re $prompt
send "exit\r"
expect eof
close;          ### TERMINATE THE SPAWN. IMPORTANT!

You also had the problem that you were using [list $file_data] when you should have broken it up by line with [split $file_data "\n"].


The above code will need to be looped over for each set of credentials. That part is most easily written as something like this (given that you really won't be having enough lines of data to need streaming processing):

set fp [open "hostnames_2.out" "r"]
set hostnamedata [split [read $fp] "\n"]
close $fp

foreach line $hostnamedata {
    lassign $line  ssh1 ssh1_usr ssh1_pwd  ods ods_usr ods_pwd  serv1 serv1_usr serv1_pwd

    # The code from above goes here
}

I separately recommend looking into setting up SSH public keys on all the intermediate servers and using the -A option to ssh. That can greatly simplify the authentication/authorization piece without compromising security.

Upvotes: 1

Related Questions