expect telnet script : can't handle "----More---" string - linux

I am new with expect scripting. I'm trying to backup Huawei router configuration with automated telnet script. I'm stuck in handling one string "---More---", it's like press any key to continue where I would like to send "\n"
My router output is like:
--------------------------------
-----------------------
voice-vlan mac-address 00d0-1e00-0000 mask ffff-ff00-0000 description Pingtel phone
voice-vlan mac-address 00e0-7500-0000 mask ffff-ff00-0000 description Polycom phone
voice-vlan mac-address 00e0-bb00-0000 mask ffff-ff00-0000 description 3com phone
#
---- More ----
And My Script is :
#!/usr/bin/expect
spawn telnet 192.168.xx.xx
expect "Username:"
send "username\n"
expect "Password:"
send "password\n"
expect ">"
# 'dis cur' is like cisco's 'show run'
send "dis cur\n"
expect "---- More ----"
send "\n"
interact
While running the script terminal throwing me this error:
bad flag "---- More ----": must be -glob, -regexp, -exact, -notransfer, -nocase, -i, -indices, -iread, -timestamp, -timeout, -nobrace, or --
while executing
"expect "---- More ----""
Can anyone help fixing this?... Thanks in advance :)

Quick answer: put -ex in front of the string that starts with a - so that the expect code can know for sure that it isn't an option.
However, aside from the quick answer you should do a somewhat more sophisticated version:
expect {
">" {
# Found prompt
}
-ex "---- More ----" {
send "\n" ;# Or maybe \r?
exp_continue ;# Keep on expecting please
}
}
interact
This has the advantage of allowing zero, one or many occurrences of the pager output.

Related

How to turn off stderr in Expect script or stop "Connection to ... closed" message after exiting?

So I have been trying to learn bash scripting with expect and I'm running into an annoying problem where even after I turn off output by setting log_user to 0, the logout message for when I exit the ssh server is still printed as "logout
Connection to blah.blah.blah closed". Going the hackish way and issuing the exit command inside the script to expect is undesirable, and neglects to print a new line in the terminal, leaving an unsightly terminal prompt behind. How do I suppress or turn off this connection closed message in my expect script? Here's a template of the code for my expect script:
#!/usr/bin/expect -f
#-f flag tells expect to read commands from file
# Set Password Prompt and Password
set PASSP "password:"
set PASSW "your_password_here\n"
# Set SFTP Prompt
set SFTP "sftp> "
# Set Remote Prompt and Project Directories
# The GL server is super weird, so make sure RPROMPT ends in the
# name of the project compilation directory and PROJDIR is the
# relative path (from home but without the tilde) of the main
# project directory.
set RPROMPT "projdir_here]"
set PROJDIR "path_to_projdir_here"
# Set SSH Address
set ADDRESS your_username_here#your_address_here
# Annoying long output turned off
log_user 0
send_user "Logging in...\n"
spawn sftp $ADDRESS
expect $PASSP
send $PASSW
expect $SFTP
send_user "Uploading necessary files...\n"
send "cd $PROJDIR\n"
expect $SFTP
send "put *.cpp .\n"
expect $SFTP
send "put *.h .\n"
expect $SFTP
send "put Makefile .\n"
expect $SFTP
send "exit\n"
spawn ssh $ADDRESS
expect $PASSP
send $PASSW
expect "]"
send "cd $PROJDIR\n"
expect $RPROMPT
send_user "Cleaning remote directory...\n"
send "make clean\n"
# Output turned on temporarily so compiler feedback can be determined
expect $RPROMPT
log_user 1
send_user "Compiling on server...\n"
send "make\n"
# Turn off output and submit (this assumes that your makefile has a 'submit'
# target with the command to submit your file there)
expect $RPROMPT
log_user 0
send_user "\nSubmitting Requisite Files...\n"
send "make submit\n"
expect $RPROMPT
send_user "Quitting...\n"
# I can't figure out how to turn off stderr in expect so that an annoying 'connection closed'
# message is printed even the log_user has been set to 0
send "logout\n"
# Transfer control to user
interact
You can see that at the end of the program, before I use the interact command, that the offending message is printed after "logout\n" is sent and the connection to the server is closed. This happens despite log_user being set to 0 above. How on earth do I turn off stderr output inside my script? I've even looked at the manual pages for expect at expect man page but there didn't seem to be much useful there. I don't understand how to turn off stderr inside an expect script so that this message isn't printed, help would be much appreciated! I really just want the script to be quiet when I'm logging out. Because it's pretty obvious that I issued a logout command, I'm not sure why this output goes to stderr. Is there a command in ssh that won't output a "Connection to...closed" message when the user exits? It would be better if I could just pipe stderr to oblivion or suppress it somehow. The normal ways don't seem to work because this is an expect script.
It is just the incorrect use of log_user. It should be used as,
log_user 0
send "logout\r"
expect eof
log_user 1
Note that I am expecting for eof pattern after sending logout\r, because it will lead to the closure of the connection. Always use \r in place of \n while using the send command.
Read here to know more about the log_user to suppress the output.
Instead of having a prompt to defined as a static string, it can be generalized as,
# We escaped the `$` symbol with backslash to match literal '$'
# The last dollar sign represents line-end.
set prompt "#|>|%|\\\$ $";
While the expect is used, we have to accompany with -re flag to specify that as a regular expression.
expect -re $prompt
SshDemo.exp
#!/usr/bin/expect
set prompt "#|>|\\\$ $"
spawn ssh dinesh#myserver
expect {
"(yes/no)" { send "yes\r";exp_continue}
"password"
}
send "welcome123\r"
expect -re $prompt
# Simply executing 'ls -l' command...
send "ls -l\r"
expect -re $prompt
log_user 0
send "logout\r"
expect eof
log_user 1
Here's my new, improved script based on the suggestions above. I also decided to use scp instead of sftp because it's much more concise and I'm only uploading files.
#!/usr/bin/expect -f
#-f flag tells expect to read commands from file
# Set Password Prompt and Password
set PASSP "password: "
set PASSW "your_password_here"
# Set Remote Prompt and Project Directories
# The ssh server is super weird, so make sure PROJDIR is the
# directory name of the main project directory and PATH is
# the relative path to the project directory from home (with the tilde)
set PROJDIR "your_projdir_name_here"
set PATH "your_projdir_path_here"
# Set SSH Address
set ADDRESS your_username_here#source_hostname_here
# Turn off annoying output
log_user 0
send_user "Logging in and uploading necessary files...\n"
# upload files via scp, (shows upload prompt for user, concise)
spawn bash -c "scp *.cpp *.h Makefile $ADDRESS:$PATH"
expect $PASSP
send "$PASSW\r"
interact
spawn ssh $ADDRESS
expect $PASSP
send "$PASSW\r"
expect "]"
send "cd $PATH\r"
expect "$PROJDIR]"
send_user "\nCleaning remote directory...\n"
send "make clean\r"
# Output turned on temporarily so compiler feedback can be determined
expect "$PROJDIR]"
log_user 1
send_user "Compiling on server...\n\n"
send "make\r"
# Turn off output and submit (this assumes that your makefile has a 'submit'
# target with the command to submit your file there)
expect "$PROJDIR]"
log_user 0
send_user "\n\nSubmitting Requisite Files...\n"
send "make submit\r"
expect "$PROJDIR]"
send_user "Quitting...\n"
send "logout\r"
expect eof
# Exit
exit

second "send" command in expect script is not waiting to finish the Output of first command

I have a very small expect script . The flow is as follows
ssh to remote machine
Execute a command and after executing the command you will be in a different prompt ( prompt name is Enter cmd>.
On the Enter cmd> prompt, I need to run many commands
Hence I designed this script
#!/usr/bin/expect
set FULL_CMD { #cmd1 #cmd2 #cmd3 }
puts "STARTING......."
log_file myfile.log ;# <<< === append output to a file
spawn ssh tempuser\#dummyserver
match_max 100000000
expect "password:"
send "temppasswd\r"
expect "*temp*"
send "cd \/home\r"
expect "*temp*"
send "new cmd prompt\r"
expect "Enter cmd>"
foreach tempcmd $FULL_CMD {
send "${tempcmd} \r "
expect -exact "Enter cmd>\r"
send -- "\r"
expect eof }
send "q"
send "exit\r"
puts "I HAVE ENDED......."
My problem: Actually the O/P of first command cmd#1 is very long and I can see that the expect script is not waiting for the O/P of the first command complete and is sending the second command cmd#2 after some time.
This script works fine on systems where O/P of cmd#1 is small, but on systems where the the O/P of cmd#1 is very large (say 1000000 lines), it has trouble, i.e. it issues the first command cmd#1 and the O/P follows, but before it get back to Enter cmd> prompt, the script issues the second command cmd#2
How can I ensure that command cmd#2 is sent only after the output of cmd#1 completes?
Try setting a timeout at the start:
set timeout 60
or use -1 to wait forever.
The problem lies in your foreach tempcmd $FULL_CMD loop:
foreach tempcmd $FULL_CMD {
send "${tempcmd} \r "
expect -exact "Enter cmd>\r"
send -- "\r"
expect eof }
The problem is that you're expecting "Enter cmd>\r", but you will never get the \r in your output.
What you should expect is "Enter cmd>$", where the $ implies the end of the current command output (and not '\nor '\r' oreof`).
foreach tempcmd $FULL_CMD {
send "${tempcmd} \r "
expect -exact "Enter cmd>$" # Check for spaces after '>' and fix as required
send -- "\r"
# expect eof
}
As for expect eof, it matches the end of the input stream which is connected to the output of the spawned process. It will only match once the spawned process terminates, thus it is of no use here
Why do you have "expect eof" in your for loop?
foreach tempcmd $FULL_CMD {
send "$tempcmd\r"
expect "Enter cmd>"
send -- "\r" # why do you need this?
}
It's smart, while developing your program, to enable debugging: add this near the top: exp_internal 1

parse information from expect command

I need to parse resulting data from a telnet/ssh command and act on the data.
As an example, I want to interact with a spawn session (ssh here), list files in current dir and collect only file of a certain extension to later execute a command on those files only.
What I've got so far:
#!/usr/bin/expect
set timeout 3
match_max 10000
set prompt {$ }
spawn ssh $user#$host
expect "password: "
send $pw\r
expect $prompt
# here's the command I need to parse resulting data
send "ls -1\r"
expect -re {(.*)\.log} {
set val $expect_out(1,string)
puts "LOG file: $val"
exp_continue
}
That script opens a ssh session, sends the command and displays all the files in current dir (log and others) but I need to process each file matching a given pattern, how can I do this?
script output:
$ DATA:
0_system.log
1_system.log
2_system.log
3_system.log
a.log
a.sh
blah
b.sh
data.csv
First of all... wouldn't ls -1 *.log be easier, and do the trick?
That said, I have found that usually you have to be very careful when using (.*), it can have at times unexpected results.
I would go with "any alphanumeric character plus underscore" (if that works for your filenames) - see my suggested expect block below. Also, keep in mind that by using $expect_out(1,string) you are saving only the filename, without the extension - not sure if that is what you want. If you want the whole thing, $expect_out(0,string) is the way to go in this case.
This will do it:
expect -re "(\[a-zA-Z0-9_\]*)\.log" {
set val $expect_out(1,string)
puts "LOG file: $val"
exp_continue
}
Hope that helps!
James
Corrected Expect probably should be:
expect {
-re {(.*)\.log} {
set val $expect_out(1,string)
puts "LOG file: $val"
exp_continue;
}
}

Using if/else in expect script

I recently updated and reformatted my /etc/hosts file and would like to run through every system (roughly 1000) to refresh my known_hosts file. I'm looking at using an expect script to send "yes" to the RSA fingerprint question. Simple enough. However, I know some of these systems are completely new to me and my password has not been set. This creates two possibilities:
"yes" is sent to the RSA fingerprint question and I'm logged into
the server. I'll then need to send an exit to close the connection
before moving onto the next host. Or...
"yes" is sent to the RSA fingerprint question and I'm presented with
the prompts to update my password starting with the current and
followed by the new password entered twice. The connection will
automatically close after the password is updated moving onto the
next host.
I think I have a basic grasp of the concept of "if/else" in expect, but I don't fully understand how to nest them, if there is a better way, or if I'm completely off-base to begin with.
This is what I have right now:
set file1 [open [lindex $argv 0] r]
set pw1 [exec cat /home/user/.pw1.txt]
set pw2 [exec cat /home/user/.pw2.txt]
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho "ssh $host"
expect {
"continue connecting"{
send "yes\r"
expect {
"current" {
send $pw2\r
} "New password" {
send $pw1\r
} "Retype new password" {
send $pw1\r
}
}
expect "msnyder"
send "exit\r"
}
interact
}
The file1 variable is the list of hosts to run the script against.
I know it isn't accurate because it errors on line 22. But, I have no idea what needs to be fixed.
Two errors I spotted:
missing close brace, probably for the "continue connecting" block
missing space before the open brace of "continue connecting". Tcl (hence Expect) is very sensitive to whitespace as it is parsed into words before the commands are evaluated. For the very few gory details, see the 12 syntax rules of Tcl.
Your code might look like:
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho "ssh $host"
expect {
"continue connecting" {
send "yes\r"
expect {
"current" {
send -- $pw2\r
exp_continue
}
"New password" {
send -- $pw1\r
exp_continue
}
"Retype new password" {
send -- $pw1\r
exp_continue
}
msnyder
}
send "exit\r"
}
}
interact
}
Notes:
exp_continue is used to "loop" back up to the expect statement: in this case, you will expect to see all of "current", "new" and "retype", so you don't want to bail out until you see your prompt.
get into the habit of typing send -- something. Without the double dash, you'll be surprised the day someone types in a password with a leading dash.
You can probably avoid having the script run like a human and just spawn an expected to fail ssh connection which will automatically accept the host RSA key and not bother with prompting for a password; you can do that later for new systems where you need to initiate a password.
Temporarily try adding this to your ~/.ssh/config file until your script is finished:
Host *
Protocol 2
PasswordAuthentication 0
StrictHostKeyChecking 'no'
CheckHostIP 'no'
UserKnownHostsFile ~/.ssh/known_hosts_new
Then when the new know_hosts_new file is loaded up, you can replace the default ~/.ssh/known_hosts and remove the UserKnownHostsFile line from your config.

how to run command on telnet command prompt in perl script

i am executing telnet command in perl script as below.
$telnetOutput = `telnet localhost 4505`;
print "\n telnet command output: $telnetOutput \n";
$clients = `clients`;
print"\n $clients\n";
$clientNumber_or_anyOtherKey = `1`;
print "\n $clientNumber_or_anyOtherKey \n";
$pollers = `pollers`;
print "\n $pollers\n";`
but after running $telnetOutput = `telnet localhost 4505`; command, as we know it will open telnet command prompt but all other commands are still executing in same old command prmopt so it's saying clients or 1 or pollers are not recognized as internal or external commands.
can any 1 help me out pls?
thanks in advanch
Communicating with external processes like telnet is more complicated than you might imagine, as you have to correctly handle buffering, waiting for input, and so on.
The canonical way to approach this is to use Expect ( https://metacpan.org/module/RGIERSIG/Expect-1.21/Expect.pod ) if you really need full interaction.
If you don't actually need interaction then a remote command runner like ssh or rsh (which you can call from perl of course) is sufficient.
this is working example for telnet connection to d-link des-1228 router and executing 2 commands. change it if you want:
#!/usr/bin/perl
use strict;
use warnings;
use Net::Telnet;
my $params;
$params{'login'}='root';
$params{'password'}='hardpass';
$params{'default_prompt'}='/DES-[^:]+:.#/'; #change it to regexp matching your prompt
my $host = '192.168.1.20';
my $t=new Net::Telnet(Timeout=>5, Prompt=>$params{'default_prompt'}, Errmode=>sub{next;});
$t->open(Host=>$host, Timeout=>2);
my $res=$t->login($params{'login'}, $params{'password'});
return if $res!=1;
$t->cmd('disable clipaging');
my #lines=$t->cmd('show fdb'); #output here
$t->close();
install TCL (ActiveTcl8.5.13.0.296436-win32-ix86-threaded.exe for windows) in system.
Then install Expect Package from Command from as teacup install Expect
run below script after modification for requirement
#!/usr/bin/expect -f
#!usr/bin/expect
package require Expect
# Test expect script to telnet.
spawn telnet localhost portnumber
expect "TradeAggregator>"
send "3\r"
expect "Client:"
send "1\r"
expect "1-Client>"
send "2\r"
expect "Client Pollers"
send "2\r"
expect "1-NYMEX UTBAPI"
send "1\r"
expect "2-NYMEX UTBAPI"
send "Test_123\r"
expect "Are"
send "n\r"
send "exit\r"
send "exit\r"
send "exit\r"
# end of expect script.

Resources