How to use 'expect' to pass UID & Password to a "for loop" in shell script?

Friends,

Need someone's help in helping me with the below requirement for a script:

> For a list of servers(over 100+), I need to login into each of them(cannot configure password-less ssh) & grab few configuration details <

I know, this is possible through expect programming in a simple shell script, but unfortunately I'm not able to get one to work as required. Below is what I could manage to write without expected results:

cat server_list  
server1 
server2 
server3 
.. 
.. 
..
cat for_loop.sh  
for i in `cat server_list` 
do 
multibos -S 
done
cat expect_script.sh  

spawn /$HOME/for_loop.sh  
expect "*User_ID*" 
send "souvikm\r" 
expect "*Password*" 
send "passw0rd\r" 
expect eof
$> ./expect_script.sh

Running the above written expect script doesn't solve my purpose - it does nothing :smiley: Could someone help me rectify this script or help me with a better idea?

-- Souvik

Here's a very basic way using Expect that should hopefully set you on the path to what you want to accomplish:

#!/usr/bin/expect -f
#
#

# set credentials - NOT RECOMMENDED METHOD
set u "username"
set p "password"

# get host name from command-line
set h [lindex $argv 0]

# set the command to run
set cmd "date"

# spawn a connection to the host and
# run the given command
spawn ssh -t $u@$h $cmd

# handle the password prompt
expect "?assword:*"
send -- "$p\r"
send -- "\r"

# done
expect eof

I've found reading files using Expect to be a bit flaky, so you could use a while loop to parse your file with the hostnames and feed it to the Expect script like so:

while read hosts;do sendCmd $hosts;done < server_list

Output:

spawn ssh -t username@server1 date
username's Password:
Mon Nov 11 08:52:42 EST 2013
Connection to server1 closed.

75d6b64809eec8e415f7bca6b09b6f40

Hello in2nix,

Your basic expect based script did the ground work for me - could login to any server passing the hostname to parameter "h", so this is how my little modified script looks now:

#!/usr/bin/expect -f
#
#

# set credentials
set u "msouvik"
set p "pass^123"

# get host name from command-line
# set h "my_desired_server"

# set the command to run
set cmd "echo Hello Souvik!  >> /export/home/msouvik/echo.out"

# spawn a connection to the host and run the given command
spawn -noecho ssh -t $u@$h $cmd

# handle the password prompt
expect "?assword:*"
send -- "$p\r"
send -- "\r"

# done
expect eof

I could not manage to pass multiple server names to this expect script, the methods I tried --

  1. Called this expect script from an external "for" & "while" loop -- got an obvious error of parameter "h" not found:
for h in `cat my_server_list`
do
<Path of above written expect script>
done
  1. Added a "for" loop within the above expect script:
#!/usr/bin/expect -f
#
#
for h in `cat my_server_list`
do

# set credentials
set u "msouvik"
set p "pass^123"

# get host name from command-line
# set h ""

# set the command to run
set cmd "echo Hello Souvik!  >> /export/home/msouvik/echo.out"

# spawn a connection to the host and run the given command
spawn -noecho ssh -t $u@$h $cmd

# handle the password prompt
expect "?assword:*"
send -- "$p\r"
send -- "\r"

# done
expect eof

done

That failed since it couldn't understand expect syntaxe(s) within the for loop.

Please further suggest!

-- Souvik

Did you try the while loop that I listed in my last post? I mentioned that reading files inside of Expect is a bit flaky. If you create a while loop to read the file containing your hostnames and then send them as an argument to the Expect script, it will store it in the h variable.

That's what this line does:

set h [lindex $argv 0]

So to put it all together, let's say the Expect script is named sendCmd:

while read hosts;do sendCmd $hosts;done < my_server_list

or 

while read hosts
do
    sendCmd $hosts
done < my_server_list

Hope that helps.

I haven't found file I/O to be flaky in expect, but I must admit I only really use expect to test solutions posted in the forum. I find it a bit of a hacking tool and would prefer to use passwordless ssh for my own work.

The below script should open the server_list file, connect to each server run the configured command ("multibos -S" in this case) and log the output to a file name config_details with a "hostname:" prefix.

Also note: I'm using telnet in this script and you may want ssh or some other command.

Uncomment the log_user 0 line to suppress output once you have finished debugging.

#!/usr/bin/expect -f
set timeout 2
set u "souvikm"
set p "passw0rd"
set command "multibos -S"

set hosts [open server_list r]
set config [open config_details w]

#log_user 0

while {[gets $hosts host] >= 0} {
    spawn -noecho telnet $host
    expect "*User_ID*"
    send -- "$u\r"
    expect "?assword*"
    send -- "$p\r"
    send "PS1='PROMPT# '\r"
    expect -re "PROMPT# '(.*)PROMPT# "
    send "$command\r"
    expect -re "\r\n(.*)\r\n(.*)PROMPT# "
    set output $expect_out(1,string)
    puts $config "$host: $output\r"
    send "exit\r"
    expect eof
}
close $hosts
close $config

Thanks in2nix, ~XL for your valuable inputs :slight_smile:

Tried tweaking in2nix's code to allign to my reqd with the following 'expect' code:

#!/usr/bin/expect -f
#
#

# set credentials
set u "msouvik"
set p "pass^123"

# get host name from command-line
set h [lindex $argv 0]

# set the command to run
set cmd "echo Hello Souvik!  >> /export/home/msouvik/echo.out.$h"

# spawn a connection to the host and run the given command
spawn -noecho ssh -t $u@$h $cmd

# handle the password prompt
expect "?assword:*"
send -- "$p\r"
send -- "\r"

# scp the data onto NIM server
spawn -noecho scp $u@$h:/export/home/msouvik/echo.out .

# done
expect eof

And then applied the below 'while' loop:

while read h
> do
> expect_script.sh $h
> done < lsnim

Below is the error I get while the script tries to create a /export/home/msouvik/echo.out.$h file on the target servers:

==[ Error ]==
msouvik@my_target_server1 password: msouvik@my_target_server1 password:
scp: /export/home/msouvik/echo.out.my_target_server1: No such file or directory

msouvik@my_target_server2 password: msouvik@my_target_server2 password:
scp: /export/home/msouvik/echo.out.my_target_server2: No such file or directory

Find it really weird 'coz the script is not able to create a file named /export/home/msouvik/echo.out.$h on the target servers, however it is able to do so without the $h suffix to the filename.

Any leads pls?

-- Souvik
P.S: I'll try to understand XL's script before I implement it tomorrow :rolleyes:

Friend's,

I expected a response from one of you today, but none :frowning:

I could manage to get my requirement by adding echo.out.$h in the scp portion of the script & not in the set cmd section as below:

cat expect_script.sh
#!/usr/bin/expect -f
#
#

# set credentials
set u "msouvik"
set p "pass^123"

# get host name from command-line
set h [lindex $argv 0]

# set the command to run
set cmd "echo Hello Buddy! >> /export/home/msouvik/echo.out"

# spawn a connection to the host and run the given command
spawn -noecho ssh -t $u@$h $cmd

# handle the password prompt for ssh
expect "Password:*"
send -- "$p\r"
send -- "\r"

# scp the data onto NIM server
spawn -noecho scp $u@$h:/export/home/msouvik/echo.out /export/home/msouvik/echo.out.$h

# handle the password prompt for scp
expect "Password:*"
send -- "$p\r"
send -- "\r"

# done
expect eof

However, this modification is creating a new problem - now the portion of code(highlighted in red) isn't working, it simply doesn't write to the file on the destination server. I've pasted the above code as it is from the server.

I'm not able to catch what happened to the working code with this small modification, could one you guys help me here? Excuse me (if) I'm forcing you guys to help :o

-- Souvik

I can't see any reason why the code in red is not working. I did notice that my ssh/scp have a lowercase P in password, however this should just cause a timeout delay before sending the password. Can I suggest using expect "?assword:*"

It it possible that the command you actually used is different to what you posted? Did you try doing the same command from the shell prompt to ensure it's working (eg output path is correct).

Perhaps you will find my script easier to work with. I can see now that you want to use ssh not telent. I've commented it some more and updated for ssh. It looks for a server_list file and produces a local config_details output file:

#!/usr/bin/expect -f
set timeout 2
set u "souvikm"
set p "passw0rd"
set command "multibos -S"

# Open server_list for read (handle hosts) and config_details for write (handle config)
set hosts [open server_list r]
set config [open config_details w]

# This like turns off local echo - 
# for debugging leave it commented and all output will be echoed
#log_user 0

# Loop through each host in the $hosts file

while {[gets $hosts host] >= 0} {
    spawn -noecho ssh -t $u@$host
    expect "?assword:*"
    send -- "$p\r"

    # Change server side prompt to make matching more reliable
    send "PS1='PROMPT# '\r"

    # Wait for new prompt
    expect -re "PROMPT# '(.*)PROMPT# "

    # Execute command
    send "$command\r"

    # Match command output and prompt
    expect -re "\r\n(.*)\r\n(.*)PROMPT# "

    # Load command output into variable output
    set output $expect_out(1,string)

    # Append output into (and host name) to local logfile
    puts $config "$host: $output\r"

    # Send exit to host and wait for connection to close
    send "exit\r"
    expect eof
}
close $hosts
close $config

Hello ~XL,

I tried your provided code(pasted as it is from the server) below:

#!/usr/bin/expect -f
set timeout 2
set u "souvikm"
set p "pass^123"
set command "oslevel -s"

# Open server_list for read (handle hosts) and config_details for write (handle config)
set hosts [open server_list r]
set config [open config_details w]

# This like turns off local echo -
# for debugging leave it commented and all output will be echoed
#log_user 0

# Loop through each host in the $hosts file

while {[gets $hosts host] >= 0} {
    spawn -noecho ssh -t $u@$host
    expect "?assword:*"
    send -- "$p\r"

    # Change server side prompt to make matching more reliable
    send "PS1='PROMPT# '\r"

    # Wait for new prompt
    expect -re "PROMPT# '(.*)PROMPT# "

    # Execute command
    send "$command\r"

    # Match command output and prompt
    expect -re "\r\n(.*)\r\n(.*)PROMPT# "

    # Load command output into variable output
    set output $expect_out(1,string)

    # Append output into (and host name) to local logfile
    puts $config "$host: $output\r"

    # Send exit to host and wait for connection to close
    send "exit\r"
    expect eof
}
close $hosts
close $config

And this is the error I get:

My_NIM_Server:msouvik:/export/home/msouvik>./expect_script_new.sh
msouvik@My_Target_Server password:
Last unsuccessful login: Thu Nov 14 12:13:52 GMT 2013 on ssh from My_NIM_Server
Last login: Fri Nov 15 13:33:34 GMT 2013 on /dev/pts/1 from 10.220.48.25
*******************************************************************************
*                                                                                             *
*                                                                                                 *
*  Welcome to AIX Version 7.1!                                                                *
*                                                                                                 *
*                                                                                                 *
*  Please see the README file in /usr/lpp/bos for information pertinent to                *
*  this release of the AIX Operating System.                                              *
*                                                                                                 *
*                                                                                                 *
*******************************************************************************
oslevel -s
My_Target_Server:msouvik:/export/home/msouvik>oslevel -s
7100-02-02-1316
can't read "expect_out(1,string)": no such element in array
    while executing
"set output $expect_out(1,string)"
    ("while" body line 19)
    invoked from within
"while {[gets $hosts host] >= 0} {
    spawn -noecho ssh -t $u@$host
    expect "?assword:*"
    send -- "$p\r"

    # Change server side prompt to mak..."
    (file "./expect_script_new.sh" line 17)
My_NIM_Server:msouvik:/export/home/msouvik>

Is there anything I missed here? Excuse me if I could not understand the logic of your provided script :o

-- Souvik

It hasn't changed the prompt correctly this:

oslevel -s
My_Target_Server:msouvik:/export/home/msouvik>oslevel -s

should have looked like this:

My_Target_Server:msouvik:/export/home/msouvik>PS1='PROMPT# '
PROMPT# oslevel -s
....
PROMPT#

Try inserting the following line (marked in red):

    send -- "$p\r"

    # Wait for original server side prompt
    expect -re "(%|#|>|\$) *$"

    # Change server side prompt to make matching more reliable
    send "PS1='PROMPT# '\r"

XL,

Implemented as you suggested & below is the o/p:

My_NIM_Server:msouvik:/export/home/msouvik>./expect_script_3.sh
msouvik@My_NIM_Client_1's password:
Last unsuccessful login: Thu Nov 14 12:13:52 GMT 2013 on ssh from My_NIM_Server
Last login: Mon Nov 18 12:18:57 GMT 2013 on /dev/pts/2 from x.x.x.x
oslevel -s
*******************************************************************************
*                                                                             *
*                                                                             *
*  Welcome to AIX Version 7.1!                                                *
*                                                                             *
*                                                                             *
*  Please see the README file in /usr/lpp/bos for information pertinent to    *
*  this release of the AIX Operating System.                                  *
*                                                                             *
*                                                                             *
*******************************************************************************
exit
My_NIM_Client_1:msouvik:/export/home/msouvik>oslevel -s
7100-02-02-1316
My_NIM_Client_1:msouvik:/export/home/msouvik>exit
Connection to My_NIM_Client_1 closed.
SECURITY NOTICE

Use of this system, its equipment, and resources is monitored at all
times. All programs and information held on this system are private and
confidential to XXX Corporation, and are only lawfully available to
authorised users for authorised purposes only.

It is a criminal offence to obtain unauthorised access to any program
or information within this system and/or to make any unauthorised
modifications whatsoever to the contents of this computer system.
If you are not an authorised user you are violating the regulations of
this system and can, and will be prosecuted to the full extent of the
law. Please disconnect immediately.
msouvik@My_NIM_Client_2's password:
Last unsuccessful login: Thu Nov 14 12:13:52 GMT 2013 on ssh from My_NIM_Server
Last login: Mon Nov 18 13:03:43 GMT 2013 on /dev/pts/4 from My_NIM_Server
*******************************************************************************
*                                                                             *
*                                                                             *
*  Welcome to AIX Version 7.1!                                                *
*                                                                             *
*                                                                             *
*  Please see the README file in /usr/lpp/bos for information pertinent to    *
*  this release of the AIX Operating System.                                  *
*                                                                             *
*                                                                             *
*******************************************************************************
oslevel -s
My_NIM_Client_2:msouvik:/export/home/msouvik>oslevel -s
exit
7100-02-02-1316
My_NIM_Client_2:msouvik:/export/home/msouvik>exit
Connection to My_NIM_Client_2 closed.

So the script ran well, but didn't fetch me the expected o/p in the "config_details" file on the source server(My_NIM_Server), contents of this file below:

My_NIM_Server:msouvik:/export/home/msouvik>cat config_details
My_NIM_Client_1:
My_NIM_Client_2:

So essentially, what I understood is - the below portion of the script fails due to some reason:

# Load command output into variable output
set output $expect_out(1,string)

# Append output into (and host name) to local logfile
puts $config "$host: $output\r"

The $host variable is captured but not the $output. Leads again pls..

-- Souvik

As I mentioned, not a big fan of parsing files within Expect, but this may work for you. It will also capture the output to a local log file:

#!/usr/bin/expect -f
#
#

# slurp in file containing hostnames
set hostfile [open "hosts" r]
set hostdata [read $hostfile]
close $hostfile

# loop through hosts, connect, and run command
foreach h [split $hostdata "\n"] {
    log_file myLog.log         # log output to a local log file
    if {$h eq {}} continue  # skip any blank lines encountered in the server file
    set u "<username>"
    set p "<password>"
    set cmd "<command>"
    spawn ssh -t $u@$h $cmd
    expect "?assword:*"
    send -- "$p\r"
    send -- "exit\r"
    send -- "\r"
    expect eof
}

abf4c35dceb02928c16476a6b803e489

I looks like you have a large delay between entering the password and the unix prompt appearing. Do you experience this delay when logging in normally?

I know AIX can experience large login delays if /etc/resolv.conf is pointing to a non-existent nameserver, basically the reverse DNS lookup of the incoming IP address stalls waiting for a response.

If you are seeing these delays you may need to extend the timeout defined at the top of the script. It might be a good debugging tool anyway, extend timeout to say 10 seconds and watch where you see pauses in the output.

You may have to tweak this command to properly match the prompts you use on each of your servers:

   # Wait for original server side prompt
    expect -re "(%|#|>|\$) *$"

Currently the above looks for any line ending in one of % # > or $ followed by zero or more spaces. This could match text in your login messages or message of the day and is why I usually change PS1 to a unique string ASAP in these type of expect scripts.

Your target is to get output like this:

My_NIM_Server:msouvik:/export/home/msouvik>./expect_script_3.sh
msouvik@My_NIM_Client_1's password:
Last unsuccessful login: Thu Nov 14 12:13:52 GMT 2013 on ssh from My_NIM_Server
Last login: Mon Nov 18 12:18:57 GMT 2013 on /dev/pts/2 from x.x.x.x
*******************************************************************************
*                                                                             *
*                                                                             *
*  Welcome to AIX Version 7.1!                                                *
*                                                                             *
*                                                                             *
*  Please see the README file in /usr/lpp/bos for information pertinent to    *
*  this release of the AIX Operating System.                                  *
*                                                                             *
*                                                                             *
*******************************************************************************
My_NIM_Client_1:msouvik:/export/home/msouvik>PS1='PROMPT# '
PROMPT #oslevel -s
7100-02-02-1316
PROMPT #exit
Connection to My_NIM_Client_1 closed.

Notice how the commands are not output during the message of the day/ login banner. The PS1= command executes first and changes the system prompt, this is very important because the output capture from oslevel needs to match on the prompt to properly capture the command output.

XL,

I would have to do some more trial & error method to make your suggested technique of this script to work -- I'll update this discussion as soon as I could get it work. FYI, the highlighted portion(in red) below doesn't work:

My_NIM_Server:msouvik:/export/home/msouvik>./expect_script_3.sh
msouvik@My_NIM_Client_1's password:
Last unsuccessful login: Thu Nov 14 12:13:52 GMT 2013 on ssh from My_NIM_Server
Last login: Mon Nov 18 12:18:57 GMT 2013 on /dev/pts/2 from x.x.x.x
*******************************************************************************
*                                                                             *
*                                                                             *
*  Welcome to AIX Version 7.1!                                                *
*                                                                             *
*                                                                             *
*  Please see the README file in /usr/lpp/bos for information pertinent to    *
*  this release of the AIX Operating System.                                  *
*                                                                             *
*                                                                             *
*******************************************************************************
My_NIM_Client_1:msouvik:/export/home/msouvik>PS1='PROMPT# '
PROMPT #oslevel -s
7100-02-02-1316
PROMPT #exit
Connection to My_NIM_Client_1 closed.

The script is not able to set the PS1 shell parameter, it does it without being able to set it as below(already shared before):

My_NIM_Server:msouvik:/export/home/msouvik>./expect_script_3.sh
msouvik@My_NIM_Client_1's password:
Last unsuccessful login: Thu Nov 14 12:13:52 GMT 2013 on ssh from My_NIM_Server
Last login: Mon Nov 18 12:18:57 GMT 2013 on /dev/pts/2 from x.x.x.x
*******************************************************************************
*                                                                             *
*                                                                             *
*  Welcome to AIX Version 7.1!                                                *
*                                                                             *
*                                                                             *
*  Please see the README file in /usr/lpp/bos for information pertinent to    *
*  this release of the AIX Operating System.                                  *
*                                                                             *
*                                                                             *
*******************************************************************************
My_NIM_Client_1:msouvik:/export/home/msouvik>oslevel -s 
7100-02-02-1316
My_NIM_Client_1:msouvik:/export/home/msouvik>exit
Connection to My_NIM_Client_1 closed.
msouvik@My_NIM_Client_2's password:
Last unsuccessful login: Thu Nov 14 12:13:52 GMT 2013 on ssh from My_NIM_Server
Last login: Mon Nov 18 12:18:57 GMT 2013 on /dev/pts/2 from x.x.x.x
*******************************************************************************
*                                                                             *
*                                                                             *
*  Welcome to AIX Version 7.1!                                                *
*                                                                             *
*                                                                             *
*  Please see the README file in /usr/lpp/bos for information pertinent to    *
*  this release of the AIX Operating System.                                  *
*                                                                             *
*                                                                             *
*******************************************************************************
My_NIM_Client_2:msouvik:/export/home/msouvik>oslevel -s 
7100-02-02-1316
My_NIM_Client_2:msouvik:/export/home/msouvik>exit
Connection to My_NIM_Client_2 closed.

in2nix4life,

Your recent suggestion gave me error too, modified code pasted below:

    +1  #!/usr/bin/expect -f
    +2  #
    +3  #
    +4
    +5  # slurp in file containing hostnames
    +6  set hostfile [open "server_list" r]
    +7  set hostdata [read $hostfile]
    +8  close $hostfile
    +9
   +10  # loop through hosts, connect, and run command
   +11  foreach h [split $hostdata "\n"] {
   +12      log_file myLog.log         # log output to a local log file
   +13      if {$h eq {}} continue  # skip any blank lines encountered in the server file
   +14      set u "msouvik"
   +15      set p "pass^123"
   +16      set cmd "oslevel -s"
   +17      spawn ssh -t $u@$h $cmd
   +18      expect "?assword:*"
   +19      send -- "$p\r"
   +20      send -- "exit\r"
   +21      send -- "\r"
   +22      expect eof
   +23  }

Error:

My_NIM_Server:msouvik:/export/home/msouvik>./expect_script.sh
wrong # args: extra words after "else" clause in "if" command
    while compiling
"if {$h eq {}} continue  # skip any blank lines encountered in the server file"
    ("foreach" body line 3)
    invoked from within
"foreach h [split $hostdata "\n"] {
    log_file myLog.log         # log output to a local log file
    if {$h eq {}} continue  # skip any blank lines ..."
    (file "./expect_script_4.sh" line 11)

Good news is, I could get my job done with pieces of code taken from each of your suggestions.
So, my expect code to run the desired cmd on target servers looks like this:

#!/usr/bin/expect -f
#
#

# set credentials
set u "msouvik"
set p "pass^123"

# get host name from command-line
set h [lindex $argv 0]

# set the command to run
set cmd "oslevel -s >> /export/home/msouvik/expect.out; multibos -S >> /export/home/msouvik/expect.out"

# spawn a connection to the host and run the given command
spawn -noecho ssh -t $u@$h $cmd

# handle the password prompt
expect "?assword:*"
send -- "$p\r"
send -- "\r"

And the code to scp the above run cmd o/p's is as below:

#!/usr/bin/expect -f
#

# set credentials
set u "msouvik"
set p "pass^123"

# get host name from command-line
set h [lindex $argv 0]

# scp the data onto NIM server
spawn -noecho scp $u@$h:/export/home/msouvik/expect.out /export/home/msouvik/expect/expect.out.$h

# handle the password prompt for scp
expect "?assword:*"
send -- "$p\r"
send -- "\r"

# done
expect eof

The scp is successfully able to get me the cmd o/p's to my NIM server suffixing with the relevant hostname as in "expect.out.$h"

You may ask, why did I choose to have 2 separate scripts for this purpose - thats coz when I club these to scripts into one - it fails to do the job(reason not known to me at the moment!)

The discussion doesn't end here until I'm able to fix both of your recent suggestions.

-- Souvik

Yes the output you have highlighted in red is the problem, as you pointed out the PS1 change is getting lost. Most likely because it is being sent too early and the login process is clearing input buffers.

Please read the first 3 paragraphs of post #13 again, here I give some clues about possible causes of this, and a propose increasing the timeout to assist debugging:

#!/usr/bin/expect -f
set timeout 25
set u "souvikm"
set p "pass^123"
set command "oslevel -s"

# Open server_list for read (handle hosts) and config_details for write (handle config)
set hosts [open server_list r]
set config [open config_details w]

# This like turns off local echo -
# for debugging leave it commented and all output will be echoed
#log_user 0

# Loop through each host in the $hosts file

while {[gets $hosts host] >= 0} {
    spawn -noecho ssh -t $u@$host
    expect "?assword:*"
    send -- "$p\r"

    # Wait for original server side prompt
    expect -re "(%|#|>|\$) *$"

    # Change server side prompt to make matching more reliable
    send "PS1='PROMPT# '\r"

    # Wait for new prompt
    expect -re "PROMPT# '(.*)PROMPT# "

    # Execute command
    send "$command\r"

    # Match command output and prompt
    expect -re "\r\n(.*)\r\n(.*)PROMPT# "

    # Load command output into variable output
    set output $expect_out(1,string)

    # Append output into (and host name) to local logfile
    puts $config "$host: $output\r"

    # Send exit to host and wait for connection to close
    send "exit\r"
    expect eof
}
close $hosts
close $config