How to pass the argument in expect shell script during runtime - shell

I am trying to pass the password dynamically, while running the expect script.
Script looks somewhat like this :
#!/usr/bin/Expect
set server [lindex $argv 0]
send "enter you password"
read Password;
send $password\n;
spawn ssh c1210427#$server ...
Got stuck while getting the password from terminal during the running script.

The [read] command reads until end of file so it's waiting for you to close the terminal. Use the [gets] command instead:
set password [gets stdin]
Also, you're using [read] wrong. The first argument is the channel id to read from. See the documentation for more info:
http://www.tcl.tk/man/tcl8.6/TclCmd/read.htm
http://www.tcl.tk/man/tcl8.6/TclCmd/gets.htm

In your code, you have used the following code like a puts statement
send "enter your password"
which is not a proper way. Usually, send command will try to send commands to the console and if any process spawned via script, then this command will be sent to that process.
Anyway, you will get the statements get printed in the console. But, be aware of it. Instead, better use send_user command.
You can try out this
#!/usr/bin/expect
set server [lindex $argv 0]
stty -echo; #Disable echo. To avoid the password to get printed in the terminal
send_user "enter you password : "
# Using regex to grab all the input till user press 'Enter'
# Each submatch will be saved in the the expect_out buffer with the index of 'n,string'
# for the 'n'th submatch string
# expect_out(0,string) will have the whole expect match string including the newline
# The first submatch is nothing but the whole text without newline
# which is saved in the variable 'expect_out(1,string)
expect_user -re "(.*)\n" ;
stty echo; #Enable echo
set pwd $expect_out(1,string)
send $pwd\n;
expect "some-other-statment"
#Your further code here
You can remove the stty -echo and stty echo if you don't bother about the password getting printed in console
Reference : http://www.tcl.tk/man/expect5.31/expect.1.html

Related

Expect script ran inside of while loop exits after processing one line

I have a while loop as such:
#!/bin/bash
doit="/pathtocommand"
file="/pathtosourcefile"
while read -r username password; do
$doit "$username" "$password"
done < $file
And my while loop command ($doit)is an expect script.
#!/usr/bin/expect -f
## Set up variables to be passed in as command line arguments
#set username [lindex $argv 0];
#set password [lindex $argv 1];
lassign $argv username password
spawn telnet 192.168.100.101 106
expect "200 PWD Server ready"
send "USER user\r"
expect "300 please send the PASS"
send "PASS password\r"
expect "200 login OK, proceed"
## Use the line below for passwords that do not have to be enclosed with quotes
send "SETACCOUNTPASSWORD $username PASSWORD $password\r"
# Use the line below for a password that must be quoted ie one that contains a $ or a ! by escaping the double quotes
#send "SETACCOUNTPASSWORD $username PASSWORD \"$password\"\r"
expect "200 OK"
send "quit\r"
interact
The expect script should run as many times as there are lines in my file. But it stops after the first line is processed. I am fairly confident that its something in the expect script because changing the command to something like echo works.
If I debug the script I see this:
+ doit=/pathtocommand
+ file=/pathtofile
+ read -r username password
+ /pathtofile 0100 01000100
spawn telnet 192.168.100.101 106
Trying 192.168.100.101...
Connected to 192.168.100.101.
Escape character is '^]'.
200 PWD Server ready
USER user
300 please send the PASS
PASS pass
200 login OK, proceed
SETACCOUNTPASSWORD 0100 PASSWORD 01000100
200 OK
quit
+ read -r username password
It looks to me like the script tries to start over but then simply exits. Can anyone help? I am in a crunch to get this thing working. I have done this before with SSH no problem. Not sure if its a telnet thing or what.
The interact command in the Expect script reads from standard input. Since standard input is redirected to the file, expect will read from the file, and there won't be anything left for the shell's while loop to read the next time.
If you want expect to interact with the terminal, you should redirect its input back to /dev/tty.
$doit "$username" "$password" </dev/tty

expect telnet to multiple cisco devices and execute show run

I have the following scripts which is supposed to pull the IP address from a file device-list.txt and then telnet to the device, login and execute a show run. The script logs in to the device successfully, but gives me an error after that. Any ideas where im going wrong?
for device in `cat device-list.txt`; do ./test4 $device;done
cat test4
#!/usr/bin/expect -f
set hostname [lindex $argv 0]
set user myusername
set pass mypassword
set timeout 10
log_file -a ~/results.log
send_user "\n"
send_user ">>>>> Working on $hostname # [exec date] <<<<<\n"
send_user "\n"
spawn telnet $hostname
expect "Username:"
send "$user\n"
expect "Password:"
send "$pass\n"
expect "#"
send “term len 0\n”
send “show running-config\n”
expect “end\r”
send “\n”
send “exit\n”
User Access Verification
Username: myusername
Password:
ROUTER#usage: send [args] string
while executing
"send “term len 0\n”"
(file "./test4" line 26)
Your problem is due to incorrect double quotes. You have to use double quotes ", not smart quotes “
send “term len 0\n” ; # Wrong
send "term len 0\r"; # Correct
Note :
Basically, Expect will work with two feasible commands such as send and expect. If send is used, then it is mandatory to have expect (in most of the cases) afterwards. (while the vice-versa is not required to be mandatory)
This is because without that we will be missing out what is happening in the spawned process as Expect will assume that you simply need to send one string value and not expecting anything else from the session.
So, we recommend to use expect after each send, to make sure our commands actually sent to the spawned process. (You have not used expect after sending some commands such as term len 0\r). Also, use \r instead of \n.

Expect script for checking ssh connection for a list of ips

Can anyone help me in creating an expect script to just do an SSH on a list of servers and check if it was fine. I do not need to interact, do not need to fire any command on each server, I just want to do an ssh and come out, with a return code mentioning whether it was successful or not.
Expect is important here, as I do not have an option to setup a passwordless connection. Also there is a possibility that passwordless connection is setup on some of those servers.
I tried something like:
#!/usr/local/bin/expect
set timeout 10
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set prompt "(>|%|\\\\\\\$|#|]|) \$"
spawn ssh "$user\#$ip"
expect "Password:"
send "$password\r"
send "echo hello\r"
expect "hello"
send "exit\r"
But this gets stuck on the first server, and does nothing after that.
Thanks,
Piyush
A generalized idea can be having a procedure to spawn the ssh and close the connection which will maintain the connections local to the scope, so that global spawn_id won't get affected at all.
#!/usr/bin/expect
proc isHostAlive {host user pwd} {
# We escaped the `$` symbol with backslash to match literal '$'
# Commonly used prompt types
set prompt "#|%|>|\\\$"
set timeout 60
spawn ssh $user#$host
expect {
timeout {return FAIL}
"(yes/no)" {send "yes\r";exp_continue}
"password:" {send "$pwd\r";exp_continue}
-re $prompt
}
set msg "Hello World"
send "echo $msg\r"
expect {
timeout {return FAIL}
"\r\n$msg"
}
send "exit\r"
expect {
timeout {return FAIL}
eof
}
return PASS
}
# Lists to maintain the each host's information
set serverList {server1 server2 server3}
set userList {user1 user2 user3}
set pwdList {pwd1 pwd2 pwd3}
# Looping through all the servers and checking it's availability
foreach server $serverList user $userList pwd $pwdList {
puts "\n==>Status of $server : [isHostAlive $server $user $pwd]\n"
}
With exp_continue, we can handle even if any host does not have password. Basically exp_continue will cause the expect to run again. So, among the mentioned phrase whichever comes, it will be handled. i.e. if expect sees (yes/no), it will send yes, if expect sees password, it will send the password value and so on. Then expect will continue to wait for the whole set of phrases again.
The reason why I have added yes/no is because if suppose the host's RSA fingerprint needs to be saved.
After successful login, I am echoing Hello World and expecting for the echoed message. If you have noticed, I have used \r\n$msg in the expect statement. Why do we need \r\n here ?
Here is why. Whenever we send command that will be seen by the expect also and it will try to match against that too. If it matched, it will proceed as such.
Sample echo command output
dinesh#dinesh-VirtualBox:~/stackoverflow$ echo Hello World
Hello World
dinesh#dinesh-VirtualBox:~/stackoverflow$
The string we want to expect is already available in the send command. So, to make sure the matching expect string is only from the actual echoed response, I have added \r\n which will help us in matching what is necessary.
Then at last of the proc, I am sending exit command which will close the ssh connection and to match the same eof (End Of File) is used. In all sort of failure cases, the procedure will return FAIL.

How to suppress expect send output?

I have an expect script which I'd like to behave as a fancy ssh program that hops several machines and sets up the environment on target machine before running the commands.
I can use log_user 0/1 to turn off / on output from expect and that helps with password prompts and login banners, and commands to setup environment.
But, like ssh, once my script starts to issue commands, I don't want to see the issued command. That is I don't want to see "command" after send "command\n". All I want to see is the results of command.
How do I suppress the send output, but not the results?
Here's a snippet of the expect script:
log_user 1
foreach daline [lrange \$argv 0 end] {
send "\$daline\r"
set buffer1
}
So prior to this loop, I send password, and setup environment. Then in this loop, I run each bash command that was fed to the expect as an argument.
thanks.
Many programs echo their input. For example, if you send the date command to the shell, you will see the string date followed by a date. More precisely, you will see everything that you would ordinarily see at a terminal. This includes formatting, too.
send "date\r"
expect -re $prompt
The command above ends with expect_out (buffer) set to date\r\nFri Nov 7 20:47:32 IST 2014\r\n. More importantly, the string date has been echoed. Also, each line ends with a \r\n, including the one you sent with a \r. The echoing of date has nothing to do with the send command.
To put this another way, there is no way to send the string and have send not echo it because send is not echoing it in the first place. The spawned process is.
In many cases, the spawned process actually delegates the task of echoing to the terminal driver, but the result is the same-you see your input to the process as output from the process.
Often, echoed input can be handled by using log_user only (which you have used in different place). As an example, suppose a connection to a remote host has been spawned and you want to get the remote date, but without seeing the date command itself echoed. A common error is to write:
log_user 0 ;# WRONG
send "date\r" ;# WRONG
log_user 1 ;# WRONG
expect -re .*\n ;# WRONG
When run, the log_user command has no effect because expect does not read the echoed "date" until the expect command. The correct way to solve this problem is as follows:
send "date\r"
log_user 0
expect -re "\n(\[^\r]*)\r" ;# match actual date
log_user 1
puts "$expect_out(l,string)" ;# print actual date only
If you are sending a lot of commands to a remote shell it may be more convenient to just disable all echoing in the first place. You can spawn a shell and then send the command stty -echo, after which your commands will no longer be echoed. stty echo re enables echoing.
spawn ssh <host>
stty -echo; # Disable 'echo' here
expect something
#Your further code here
stty echo # Enable 'echo' here
#Close of connection
Reference : Exploring Expect

Bash/Expect Script for SSH

I am new to Expect and scripting in general. I am trying to make a few scripts to make my life a bit easier when pulling network device configurations. I managed to create a basic Expect script to SSH to a device and save the configuration.
I want to expand upon this and allow the script to connect to a number of IP addresses instead of just one like I have right now. I have a file named list.txt with a few different IP addresses with each IP address on a separate line.
What would I need to do to have the Expect script connect to each of these IP addresses and perform the rest of the tasks in the script as well?
Here is the Expect script I have so far:
#!/usr/bin/expect -f
# Tells interpreter where the expect program is located. This may need adjusting according to
# your specific environment. Type ' which expect ' (without quotes) at a command prompt
# to find where it is located on your system and adjust the following line accordingly.
#
#
# Use the built in telnet program to connect to an IP and port number
spawn ssh 192.168.1.4 -l admin
#
# The first thing we should see is a User Name prompt
#expect "login as:"
#
# Send a valid username to the device
#send "admin"
#
# The next thing we should see is a Password prompt
expect "Password:"
#
# Send a valid password to the device
send "password\n"
#
# If the device automatically assigns us to a privileged level after successful logon,
# then we should be at an enable prompt
expect "Last login:"
#
# Tell the device to turn off paging
#
# After each command issued at the enable prompt, we expect the enable prompt again to tell us the
# command has executed and is ready for another command
expect "admin#"
#
# Turn off the paging
send "set cli pager off\n"
#
# Show us the running configuration on the screen
send "show config running\n"
#
# Set the date.
set date [timestamp -format %C%y%m%d]
#
# Test output sent to file with a timestamp on end
#-noappend will create a new file if one already exists
log_file -noappend /home/test.cfg$date
#
expect "admin#"
#
# Exit out of the network device
send "exit\n"
#
# The interact command is part of the expect script, which tells the script to hand off control to the user.
# This will allow you to continue to stay in the device for issuing future commands, instead of just closing
# the session after finishing running all the commands.`enter code here`
interact
Do I need to integrate this with a Bash script? If so, is it possible to read one line of the list.txt file, use that as the IP address/host variable and then read the next and repeat?
I would do this (untested):
#!/usr/bin/expect -f
set logfile "/home/text.cfg[clock format [clock seconds] -format %Y%m%d]"
close [open $logfile w] ;# truncate the logfile if it exists
set ip_file "list.txt"
set fid [open $ip_file r]
while {[gets $fid ip] != -1} {
spawn ssh $ip -l admin
expect "Password:"
send "password\r"
expect "admin#"
send "set cli pager off\r"
log_file $logfile
send "show config running\r"
expect "admin#"
log_file
send "exit\r"
expect eof
}
close $fid
Notes:
I removed all your comments for brevity
use \r to simulate hitting enter when you send commands.
I assumed you only want to log the "show config running" output
use expect eof after you send "exit"
This is a Perl version for this issue:
Install instruction:
cpan Expect
This script works perfectly for my needs.
Parameter 1: Connection string (example: admin#10.34.123.10)
Parameter 2: Clear text password
Parameter 3: Command to execute
#!/usr/bin/perl
use strict;
use Expect;
my $timeout = 1;
my $command = "ssh " . $ARGV[0] . " " . $ARGV[2];
#print " => $command\n";
my $exp = Expect->spawn($command) or die "Cannot spawn $command: $!\n";
$exp->raw_pty(1);
LOGIN:
$exp->expect($timeout,
[ 'ogin: $' => sub {
$exp->send("luser\n");
exp_continue;
}
],
[ 'yes\/no\)\?\s*$' => sub {
$exp->send("yes\n");
goto LOGIN;
}
],
[ 'assword:\s*$' => sub {
$exp->send($ARGV[1]."\n");
#print "password send: ", $ARGV[1];
exp_continue;
}
],
'-re', qr'[#>:] $'
);
$exp->soft_close();
A possibility is to pass the IP address as a parameter in your Expect script:
set host_ip [lindex $argv 0]
and then make a shell script, calling your Expect script inside a while loop:
ips_file="list.txt"
while read line
do
your_expect_script line
done < $ips_file
Or use set ip [gets stdin] to the IP address from the user input.
For example,
puts "Enter your IP address\n"
set ip [get stdin]
Use this in spawn. We can do the same for multiple IP addresses using a loop -
spawn ssh $ip -l admin
Here's a good way to integrate expect with bash:
ssh_util.expect-
#!/usr/bin/expect
set timeout -1
set ip [lindex $argv 0]
set user [lindex $argv 1]
set pwd [lindex $argv 2]
set commands [lrange $argv 3 [llength $argv]]
spawn ssh -o LogLevel=QUIET -t $user#$ip $commands
expect {
yes/no {send "yes\r" ; exp_continue}
*?assword {send "$pwd\r" ; exp_continue}
}
You can run this in the terminal with ./ssh_util.expect <commands...>. In your shell script you can use it to run commands on your host machine like this:
example.sh -
#! /bin/bash
# ssh_exp <commands...>
ssh_exp () {
./ssh_util.expect 192.168.1.4 username password $*
}
# run commands on host machine here
ssh_exp ls
ssh_exp ls -la
ssh_exp echo "Echo from host machine"
# you can even run sudo commands (if sudo password is same as user password)
ssh_exp sudo apt install
Make sure to run
chmod +x ssh_util.expect
chmod +x example.sh
in the terminal to make both files executable. Hope this helps!

Resources