Linux - GeneralThis Linux forum is for general Linux questions and discussion.
If it is Linux Related and doesn't seem to fit in any other forum then this is the place.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
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.
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"
}
}
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):
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.
Just started to playing around with expect again. This is how far I've gotten and now I am stuck again. 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.
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.
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.
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.
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?
Last edited by ElFredrico; 10-21-2006 at 03:07 PM.
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.
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
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.