Using expect script inside started telnet session - bash

I am using an expect script to interact with telnet session, which started earlier from bash. The script dont listen for symbols in command line. It just send commands after timeout runs out. I am sure, that i entered the right symbol to expect (), because the script works, when i run it standalone.
I have list of command (1 command per line) to send to mikrotik. My expect script puts every command to same line and then the script send them. I separated commands with ; and it worked, but just for a short command. So I decided to separate file with commands to smaller files. Then I open telnet session (in bash) and start expect script, which opens smaller files and send commands over and over. So I don´t need to restart same session over and over. Problem is, that expect script don´t send command, when it gets awaited symbol, but it sends commands after a timeout runs out.
This is part of bash script. It could be easier, to do whole script in Expect, but I didn't add loop and if condition yet and when i tried to create some while or if structure, I failed in it.
(
echo open "12.12.13.44"
sleep 2
echo "admin+t"
sleep 2
echo "admin"
sleep 2
/usr/bin/expect /home/toor/expect2.sh
) | telnet
Here is expected symbol
expect "> "
and here is the part of expect script, which puts all commands to same line (without sleep 1 command, script sends quit in the middle of reading from file)
set timeout 1
set fid [open /home/toor/file.txt]
set content [read $fid]
close $fid
set records [split $content "\r"]
foreach record $records {
lassign $records \
commands
expect "> "
send "$commands\r"
}
sleep 1
expect "> "
send "quit\r"
I will be grateful for any advice regarding my problem with the expecting for symbol, as well as with writing to same line.

You are piping the output of expect into telnet; there is no way for expect to receive information back from the telnet process in this scenario.
I would approach this as a chain of expect scripts instead - write one which starts the session, then hands over control to your existing script.

Related

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

Expect Script - Starting logging from a certain point, and stop at a certain point

Hope you're all well!
I've recently begun putting together a little utility for a few our of network engineers to use. It's function is to retrieve circuit information which is then parsed and outputted in a nice format. Of course, part of this involves an SSH connection to the box required and running the command to generate the desired output.
Here's the script I've currently got. It works fine, runs the desired command, sends a few blank spaces to prompt the system to display more circuits, and then finishes off by exiting. All of this is being logged into a file with a name identical to the box's hostname. Great.
However my issue is apparent when I read the file produced and see that it includes a ton of data, including the command I ran and unnecessary stats provided on connection. I'm only looking for the logging to begin when I issue the command, and for it to cut off afterwards. As I'm not familiar with expect, I'd really appreciate any guidance.
PS. Apologies if I've done anything stupid; I'm pretty new to the language itself and the support out there isn't that great.
Thanks.
set ip [lindex $argv 0]
set hostname [lindex $argv 1]
set timeout 10
set user ""
set password ""
# Spawning the ssh session
spawn ssh $ip -l $user
# Awaiting prompt for password
expect "$user#$ip's password:"
sleep 1;
# Awaiting prompt
send "$password\n"
sleep 2
log_file -noappend $hostname;
send "terminal length 0\n"
sleep 1
send "show int desc\n"
sleep 5
send "exit\n"
interact
exit
You can control the output by means of placing the log_file in your desired place.
When you want to start logging, you can add the line as (which you already have in your code)
log_file my_log_file.log
Whatever printed in console will be logged after these command execution in to the file named my_log_file.log. By default, if the file is already present in the location, it will be appended. If you add the flag -noappend, then it will overwrite the existing file.
log_file -noappend fresh.log
If you want to stop the logging, then simply give the log_file without any arguments as such
log_file
From this point, whatever output generated will not be saved.
For example, you are logging to the some switch and giving some credentials and executing some commands can be something like as follows,
spawn ssh some_ip
expect some_thing
#Login code here
log_file output.log; # Start logging
#some actions here
log_file; # stopping logging here.
# some more actions
# end
You are sending the spaces for multiple times. Instead, you can set the terminal length to 0 as follows,
send "term len 0"
expect "prompt"
This will avoid the overhead of the sending it multiple times. Also, in your case, the spaces will be sent to the switch very fast, since there is nothing to expect. If you are still interested to do it without 'term len 0' , then at least you can put the code in a loop, like as follows,
for { set i 0 } { $i < 10 } { incr i } {
send " "
expect "something"
}
But, this way of doing is not advisable, since there is a possibility of need to send more than 10 spaces, then this will fail.

Expect exits too soon

I have the following bash script (script.sh):
#!/bin/bash
read -p "Remove? (y|n): " answer
echo "You answered '$answer'."
and I would like to drive it using expect. I have the following script (expect.exp, in the same directory):
#!/usr/bin/expect -f
set timeout -1
spawn -noecho ./script.sh
expect "^Remove"
send "y\r"
but it doesn't work as expected (pun intended). The result is:
~/Playground$ ./expect.exp
Remove? (y|n): ~/Playground$
So, the expect script somehow fails on the first 'expect "^Remove"' line and exits immediately, and the rest of script.sh does not execute. What am I doing wrong here?
I have been following the basic tutorials found online (the ones with the ftp examples). I am using expect 5.45 on Kubuntu 12.10.
Edit
So it changes if I add either 'interact' or 'expect eof' at the very end. But I have no idea what happens and why. Any help?
Two things I see:
"^Remove" is a regular expression, but by default expect uses glob patterns. Try
expect -re "^Remove"
while developing your program, add exp_internal 1 to the top of the script. Then expect will show you what's happening.
Ah, I see that expect adds special meaning to ^ beyond Tcl's glob patterns.
However, because expect is not line oriented, these characters (^ and $) match the beginning and end of the data (as opposed to lines) currently in the expect matching buffer
So what you see is that you send y\r and then you expect script exits as it has nothing more to do. When your script exits, the spawned child process will be killed. Hence the need to wait for the spawned child to end first: expect eof
Problem
You are not matching any text after the shell script's prompt, so the buffer for your spawned process never gets printed. The script finishes, the spawned process closes, and that's the end of the story.
Solution
Make sure you expect a specific response or your shell script's EOF, and then print your buffer. For example:
#!/usr/bin/expect -f
spawn -noecho "./script.sh"
expect "Remove" { send "y\r" }
expect EOF { send_user $expect_out(buffer) }

Problems with terminating connection after running scripts on remote computer using shell script

This is the first time I am writing a shell script. I tried to do as much research as I can to avoid dumb/repetitive question. Please excuse if its repeat/dumb question.
I have a shell script which connects to remote linux machine and runs scripts there. I am using 'expect' to spawn a ssh connection and to issue commands to trigger the job. However, I am having issues while closing the connection after completing the job.
This is my script:
set prompt "(%|#|\\$|%\]) $"
expect -c 'spawn ssh $UN#$STAGE ;
expect password ; send "$PASS \n";
expect -regexp "$PROMPT"; send "./settings.$UN.sh > settings_log.txt \n";
interact'
This script successfully runs the script file for me ($UN and $STAGE parameters are input to the script. I omitted that here for simplicity). However, this leaves me with an open connection.
I tried to close the connection after running the script by using following instead of above
expect -c 'spawn ssh $UN#$STAGE ;
expect password ; send "$PASS \n";
expect -regexp "$PROMPT"; send "./settings.$UN.sh > settings_log.txt \n";
expect -regexp "$PROMPT"; send "exit \n"'
This does close the connection but I noticed that my script file did not run at all. Also the settings_log.txt is not generated at all.
Does this mean, that exit command is aborting the process before its completion? I tried using 'sleep' before exit but it did not help. Is there a better suggested way to terminate the connection when using expect?
Any help is appreciated.
with expect, you terminate your send commands with \r not \n, so
expect -c 'spawn ssh $UN#$STAGE
expect password
send "$PASS\r"
expect -regexp "$PROMPT"
send "./settings.$UN.sh > settings_log.txt\r"
expect -regexp "$PROMPT"
send "exit\r"
expect eof'
Note you can execute remote shell commands and copy files using ssh and scp, directly, without using expect.
For example,
scp ./settings.$UN.sh $UN#$STAGE:settings_log.txt
ssh $UN#$STAGE whatever-you-need-to-execute
The connection will close as soon as soon as whatever-you-need-to-execute completes.
Your outer script seems to be written in csh and sets a variable named "prompt", but your expect script is using a variable called "PROMPT". Try making the two variable names match case.

Conditional statament inside expect command called from a bash script

I've been trying to automate some configuration backups on my cisco devices, i've already managed to do the script that accomplishes the task but I'm trying to improve it to handle errors too.
I think that's necessary to catch the errors on two steps, first just after the 'send \"$pass\r\"' to get login errors (access denied messages) and at the 'expect \": end\"' line, to be sure that the commands issued were able to pull the configuration from the device.
I've seen some ways to do it if you work on a expect script, but i want to use a bash script to be able to supply a list of devices from a .txt file.
#!/bin/bash
data=$(date +%d-%m-%Y)
dataOntem=$(date +%d-%m-%Y -d "-1 day")
hora=$(date +%d-%m-%Y-%H:%M:%S)
log=/firewall/log/bkpCisco.$data.log
user=MYUSER
pass=MYPASS
for firewall in `cat /firewall/script/firewall.cisco`
do
VAR=$(expect -c "
spawn ssh $user#$firewall
expect \"assword:\"
send \"$pass\r\"
expect \">\"
send \"ena\r\"
expect \"assword:\"
send \"$pass\r\"
expect \"#\"
send \"conf t\r\"
expect \"conf\"
send \"no pager\r\"
send \"sh run\r\"
log_file -noappend /firewall/backup/$firewall.$data.cfg.tmp
expect \": end\"
log_file
send \"pager 24\r\"
send \"exit\r\"
send \"exit\r\"
")
echo "$VAR"
done
You need alternative patterns in the expect statements where you want to catch errors. If you're looking for a specific error message you can specify that, alternatively just specify a timeout handler which will eventually trigger when the normal output fails to appear.
Eg. after send \"$pass\r\" instead of expect \">\" try:
expect \">\" {} timeout {puts stderr {Could not log in}; exit}
ie. if the expected output arrives before the timeout (default 10 sec) do nothing and continue, otherwise complain and exit from expect. You might also need an eof pattern to match the case where your ssh session ends.
Note that since you don't do any variable substitution in expect, you don't need \"\" around your strings, you can use {} or even nothing when it's one word, eg. expect conf and send {no pager}.
BTW I agree with bstpierre that this would be cleaner if you dropped bash and did the whole thing in expect, but if bash does the job that's ok.
If you don't use single quotes (expect -c '...'), then all the $variables will be substituted by bash not expect. May be easier to put the expect code in a separate file, or maybe a heredoc.

Resources