LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - General (https://www.linuxquestions.org/questions/linux-general-1/)
-   -   Need help with BASH-scripts and EXPECT (https://www.linuxquestions.org/questions/linux-general-1/need-help-with-bash-scripts-and-expect-493245/)

ElFredrico 10-17-2006 01:49 PM

Need help with BASH-scripts and EXPECT
 
Hi Guys,

I'm basically a router guy and know just enough linux to get by daily. I am trying to get a bash-script with Expect running that helps me to mass-send telnetcommands to 500 Cisco routers I manage.

What I want to do is:
I have 2 textfiles. One textfile with ip-addresses of the hosts I want to telnet to (called targets.txt) and one textfile that contains the commands (called commands.txt) that I want to send to each and every ip-address in targets.txt.

Example targets.txt looks like this:
10.0.0.1
10.0.0.2

Example commands.txt looks like this:
configure terminal
interface fa0/0
ip address 192.168.0.1 255.255.255.0
end
quit

I have a Expect-script that telnets to the router and logs in, but I have no idea how to solve the script that needs to read from two files to get the variables.

Now, I'm guessing I need some 'foreach ' statements in a bash-scripts or somthing. But this is were my linux-skills stops. Really need some help over here.

Appreciate any comments you might have!

Best Regards,
Fredrik

matthewg42 10-17-2006 08:21 PM

There's no need to wrap up expect code in a bash script - expect provides functions for reading from files etc. Having just one program file is typically easier to maintain.

Here's a little expect code to get you started:

Code:

#!/usr/bin/env expect

set load_fh [open "ip_list_file" r]
set ip_list [split [read $load_fh] "\n"]
close $load_fh

foreach ip $ip_list {
    if {$ip != ""} {
        send_user "processing IP $ip\n"
    }
}


ElFredrico 10-18-2006 02:39 AM

Hey Matthew,

Thanks! I think I understand, I'll try this tonight and let you know how it turned out.

Regards,
Fredrik

matthewg42 10-18-2006 03:32 AM

This example doesn't include error checking, but we can discuss that later. Let's get the basics in place first.

A hint for the login process - expect runs other programs by calling the spawn procedure and then interacts with them using the send procedure and expect is used to scan the output of the called program for some pattern (and thus decide what to send to it). send_user is used to print output from the expect script.

Typically an automated login process might look something like this (assuming you've previously set up variables ip, username and password):

Code:

eval spawn "telnet $ip"
expect "^Login:"
send "$username\n"
expect "Password:"
send "$password\n"
expect "prompt> "
send "configure terminal\n"
expect "prompt> "
send "interface fa0/0\n"
...

This is the simplest way to do expect ... send. You can also expect multiple things at once and perform an action based on which one is encountered. This is useful for logging in with ssh because ssh might thrown up one of many messages if the host hasn't been logged into before and so on:

Code:

expect {
    "Are you sure you want to continue connecting" {
        send_user "Adding host to ssh known hosts list...\n"
        send "yes\n"
        exp_continue
    }
    "Do you want to change the host key on disk" {
        send_user "Changing host key on disk...\n"
        send "yes\n"
        exp_continue
    }
    "$the_user's password: " {
        send "[get_pass]\n"
    }
}

Note that you probably DON'T want to automatically add new host keys with ssh - the warnings about changed keys should probably be handled by the user.

Another thing you can see here is a procedure call. The line:

Code:

        send "[get_pass]\n"
...is a procedure call. You use the square brackets to call fuctions, in this example called get_pass which presumably determines the correct password for the account.

The last thing about this example is exp_continue, which tells expect to stay in the current expect block after the current match.

ElFredrico 10-21-2006 03:51 AM

Hey Matthew,

Just started to playing around with expect again. This is how far I've gotten and now I am stuck again. :confused: The script seems to run fine, but I cannot get the script to send the actual commands from the commands.txt. See the script below.

Code:

#!/usr/bin/expect --

# Usage
set timeout 5

set loginname [lindex $argv 0]
set vtypassword [lindex $argv 1]
set enablepassword [lindex $argv 2]

set load_fh [open "targets.txt" r]
set ip_list [split [read $load_fh] "\n"]
close $load_fh

foreach ip $ip_list {
        if {$ip != ""} {
                send_user "telnet to this host: $ip\n"

                # Connect
                spawn telnet $ip

                expect "sername:" {
                send "$loginname\n"
                sleep 0.5
                expect "assword:"
                send "$vtypassword\n"
                sleep 0.5
                expect ">" {
                  send "enable\n"
                  expect "assword:"
                  send "$enablepassword\n"
                  send "term length 0\n"
                }
                send "$vtypassword\n"
                sleep 0.5
                expect ">"
                send "enable\n"
                expect "assword:"
                send "$enablepassword\n"
                sleep 0.5
                expect "#"
                send "term length 0"
                } \
                "ogin:" {
                send "$loginname\n"
                sleep 0.5
                expect "assword:"
                send "$vtypassword\n"
                }

                set load_cmd [open "commands.txt" r]
                set cmd_list [split [read $load_cmd] "\n"]
                close $load_cmd

                foreach command $cmd_list {

                        if {$command != ""} {
                                send_user "%% begin send commands %%\n"
                                send "$command\n"
                                send_user "%% end send commands %%\n"
                                send "logout\n"
                        }
                }
        }

Now, the results are a bit confuing. This is what is happening.

Code:


./expectscripter admin "my secret password"

spawn telnet 1.1.1.1
Trying 1.1.1.1...
Connected to 1.1.1.1.
Escape character is '^]'.

**************************************************
*  Access only allowed for authorized personel!  *
*          All actions are logged!              *
**************************************************


User Access Verification

Username: admin
Password:

router#%% begin send commands %%
%% end send commands %%

The script does not send the commands from the 'send "$command\n"' line. If I do a 'send_user "$command\n"' I can see that the command get echoed back to me.

Do you have any suggestions?

Thanks,
Fredrik

matthewg42 10-21-2006 04:31 AM

It looks OK to me so long as the commands.txt contains something. [scratches head].

The only thing I can think of is that maybe the remote device (or rather the telnet protocol) expects something \r\n or \r instead of \n to tell it that you've pressed return. This only needs to be changed after login, so just change the
Code:

send "$command\n"
to
Code:

send "$command\r"
or
Code:

send "$command\r\n"
One more minor point, consider moving the read of the command.txt file to the top of the file, so you only have to read the file once.

Matthew

ElFredrico 10-21-2006 09:24 AM

Alright. I'll give it a try.

Note, I had the procedure for read command.txt at the top before. I changed to see if there were any change. I'll let you know what happened.

Thanks,
Fredrik

ElFredrico 10-21-2006 11:28 AM

Damn. It does not seem to work, doesn't matter what I do. Do you know if there's any limitation to having a foreach statement within another?

Regards,
Fredrik

matthewg42 10-21-2006 11:34 AM

I'm pretty sure nested foreach's are OK. It's seems to be getting to the correct section because you can see the "%% begin send commands %%" message in the output.

There is a program called autoexpect with which you can "record" a session. It generates a script which you can then re-run at another time. The scripts it produces are a little ugly, but it would be a good way to see exactly what needs to get sent, and then you can modify your script as necessary.

matthewg42 10-21-2006 11:42 AM

One more thought. Where you would send the command, use the "interact" command. This connects your terminal to the remote session as if you had logged in manually. Try to type in the commands manually to check that the login is OK.

ElFredrico 10-21-2006 03:05 PM

If I substitute the "send command\n" with interact, everything works fine. I can interact with the router just fine.

Code:

[root@netserver02:/var/www/localhost/php]# ./expectscripter "admin" "secretpassword"
telnet to this host: 1.1.1.1
spawn telnet 1.1.1.1
Trying 1.1.1.1...
Connected to 1.1.1.1.
Escape character is '^]'.

**************************************************
*  Access only allowed for authorized personel!  *
*          All actions are logged!              *
**************************************************


User Access Verification

Username: admin
Password:

Router#
Router#sh run int lo0      <<<this is me typing
Building configuration...

Current configuration : 69 bytes
!
interface Loopback0
 ip address 1.1.1.1 255.255.255.255
end

Router#quit
Connection closed by foreign host.
end processing host: 1.1.1.1

Bummer. If you want you can telnet set up my script to telnet into these two public route-servers on the internet:
route-server.cerf.net
route-server.ip.tiscali.net

and do the ios-command "show ip bgp 192.168.0.0/16" It will probably say that the network don't exists buts thats alright. Just to prove a point here.

Code:

#!/usr/bin/expect

set timeout 5

set loginname [lindex $argv 0]
set vtypassword [lindex $argv 1]
set enablepassword [lindex $argv 2]


set load_fh [open "targets.txt" r]
set ip_list [split [read $load_fh] "\n"]
close $load_fh

set load_cmd [open "commands.txt" r]
set cmd_list [split [read $load_cmd] "\n"]
close $load_cmd


foreach ip $ip_list {
        if {$ip != ""} {
                send_user "telnet to this host: $ip\n"

                # Connect
                spawn telnet $ip

                expect "sername:" {
                sleep 0.5
                send "$loginname\n"
                expect "assword:"
                sleep 0.5
                send "$vtypassword\n"
                set timeout 1
                expect ">" {
                  sleep 0.1
                  send "enable\n"
                  expect "assword:"
                  sleep 0.5
                  send "$enablepassword\n"
                }
                } \
                "assword:" {
                send "$vtypassword\n"
                expect ">"
                send "enable\n"
                expect "assword:"
                send "$enablepassword\n"
                } \
                "ogin:" {
                send "$loginname\n"
                expect "assword:"
                send "$vtypassword\n"
                }

                set timeout 5

                foreach command $cmd_list {
                        if {$command != ""} {
                                send_user "%% begin send commands %%\n"
                                send -- "command\n"
#                              interact
                                send_user "%% end send commands %%\n"
                        }
                }
                send_user "end processing host: $ip\n\n"
        }
}


Anything?

matthewg42 10-22-2006 05:49 AM

OK, I re-wrote the script a little and it works. I think (but I'm not certain) that the problem was with some expect statements not finishing before you sent your commands.

commands.txt (my example):
Code:

help
show ip bgp 192.168.0.0/16

targets.txt:
Code:

route-server.cerf.net
route-server.ip.tiscali.net

test script:
Code:

#!/usr/bin/expect

set timeout 5

set load_fh [open "targets.txt" r]
set ip_list [split [read $load_fh] "\n"]
close $load_fh

set load_cmd [open "commands.txt" r]
set cmd_list [split [read $load_cmd] "\n"]
close $load_cmd

send_user "Starting up now..."

foreach ip $ip_list {
    if {$ip != ""} {
        send_user "connecting to: $ip...\n"

        # Connect
        spawn telnet $ip
        set prompt "route-server.*>"

        foreach command $cmd_list {
            if {$command != ""} {
                expect -re $prompt {
                    send_user "\nfound prompt, sending command: \"$command\"\n"
                    send "$command\r"
                }
            }
        }

        expect -re $prompt {
            send_user "\nfound prompt, sending command: \"exit\"\n"
            send -- "exit\r"
        }
               
        send_user "I'm done with $ip\n"
        close
    }
}

send_user "all done\n"

The real trick here is choosing a good value for "prompt". This is a regular expression which must match the prompt. The thing is, if you match something in the device's login message (before the actual prompt), it'll send the command too soon, and that may not work.

If the devices to which you wish to connect are consistent in how they display the prompt, this won't be too hard. However, I notice the two examples you gave me had different ways of constructing the prompt. The first just says "route-server>", but the second one says "route-server.ip.tiscali.net>". Fortunately the regular expression "route-server.*>" matches both, but this may not apply to all your devices... It'll be a matter of research on your part to see what all the different devices do and make some code to handle it.

Hope that helps

robhumes 01-18-2011 03:04 PM

Expect script modification
 
Matthew, Thanks for the script. I've been tasked to test connectivty to a our customers routers and switches as part of the onboarding process. One problem I've run into has been the differences in account access. Some devices are accessed via telnet, some via SSH. These devices may also have different usernames and passwords.

I'd like to be able to import more than just the Host to connect to. Maybe a .csv file that contains:
AccessMethod Hostname Username Password
Telnet 172.1.1.1 Cisco Ci$c0123
SSH 172.1.1.2 Cisco Ci$c0123

The script would then spawn the correct access based on what's read from AccessMethod.
The output would need to be written to a file for review.
Any help would be greatly appreciated. thanks

matthewg42 01-18-2011 03:15 PM

I'll write you a script if you pay me. :-)

robhumes 02-03-2011 11:57 AM

I'm interested in having you create a script.

vanid 03-22-2011 06:47 AM

HI

I too in need of the above code for telnet. here I not understood how set up a prompt:

i.e here in target.txt i hav enot uderstoo what they mean
route-server.cerf.net
route-server.ip.tiscali.net

for telnet to invoke...we need login name and password...where they are here

please anyone do clarify my doubt....and thanks

ThemHuyen 04-27-2012 08:16 AM

The error was due to the fact that Cisco router/switch doesn't not accept any command that is not Cisco's such "found prompt, sending command:..." and also Mathewg42's suggested code seems to have a missing the EOF to ensure the script is not terminated too early. Below is my suggested script, it is for ssh (tested and worked fine with Solaris). However, you can modify it to support telnet session (just take extra care with the prompt as telnet may ask for the userid and password more than once).

(solaris)$ cat test.exp

#!/usr/local/bin/expect
#Written by ThemHuyen. Date 27/04/2012
#
#
#
set timeout 20
set pwd "your_passwd_goes_here"
set file [open target.txt r]
set ip_list [split [read $file] "\n"]
close $file
foreach host $ip_list {
if {$host != ""} {
spawn ssh -o StrictHostKeyChecking=no $host
expect {
"ssword:" { send "$pwd\r" }
}
set load_cmd [open "commands.txt" r]
set cmd_list [split [read $load_cmd] "\n"]
close $load_cmd
expect {
{#} { foreach command $cmd_list {
if {$command != ""} {
send "$command\n"
}
}
}
}
expect EOF
}
}

########################

(solaris)$ cat commands.txt
show ip int brief
show int sum
show clock
exit

########################

(solaris)$ cat target.txt
10.10.10.10
11.11.11.11
12.12.12.12
13.13.13.13

########################

jayaramprasad 05-17-2012 11:51 PM

Hi,

I have a requirement like below

I have a bash script by executing it it will ask the few inputs like IPaddress, hostname,
path to install the application etc...

so i wanted to automate this using expect without modifying that script

How can i pass the inputs from another script using expect to the main script.


All times are GMT -5. The time now is 08:16 AM.