Expect: How does one export the executing environmt to the runtime environment? (where keys are sent) - expect

I have tried the following:
set timeout -1
spawn $env(SHELL)
match_max 100000
send -- "ssh somewhere#addr"
expect "*"
for { set i 0 } { $i < [array size env] } { incr i } {
send -- "echo env($i) = $env($i)"
expect "*"
}
send -- "env > foo\r"
expect "*"
send -- "^D"
expect eof
The above fails with the following error:
no such variable
(read trace on "::env(0)")
invoked from within
"send -- "echo env($i) = env($i)""
("for" body line 2)
invoked from within
"for { set i 0 } { $i < [array size env] } { incr i } {
send -- "$::env($i)"
expect "*"
}"
(file "./script.exp" line 52)
I'll get around system issues by re-sourcing the rc files; I merely want to transport an unknown collection of vars (relative to the script) set by me to the remote execution environment.
Is this possible, I am unfamiliar with TCL (total beginner)?

Tcl arrays are more like a dictionary of keys and values than a list indexed by number. You can get the list of keys with [array names env] and traverse them with foreach:
foreach n [array names env] {
send -- "echo env($n) = $env($n)\r"
expect "\n"
}

Related

Fetch using mtlogin not working on RouterOs 7.1.5

I am currently encountering a problem.
When I want to download a file on a mikrotik in 6.48.6 using mtlogin and fetch tool, it works perfectly and the script waits until the router has finished downloading to send a "quit".
However, when trying the same manipulation on a router in version 7.1.5, the "quit" is sent directly, thus stopping the download because of the letter Q and thus sending "uit" thereafter in the prompt.
The prompts are similar for 6.48.6 and 7.1.5, and even when trying to add expects in the script, the result is the same.
I think the problem is in this part of the code, but don't know how to fix it.
# Run commands given on the command line.
proc run_commands { prompt command } {
global do_interact in_proc
set in_proc 1
# escape any parens in the prompt, such as "(enable)"
regsub -all "\[)(]" $prompt {\\&} reprompt
# handle escaped ;s in commands, and ;; and ^;
regsub -all {([^\\]);} $command "\\1\u0002;" esccommand
regsub -all {([^\\]);;} $esccommand "\\1;\u0002;" command
regsub {^;} $command "\u0002;" esccommand
regsub -all {[\\];} $esccommand ";" command
regsub -all {\u0002;} $command "\u0002" esccommand
set sep "\u0002"
set commands [split $esccommand $sep]
set num_commands [llength $commands]
for {set i 0} {$i < $num_commands} { incr i} {
send -- "[subst -nocommands [lindex $commands $i]]\r"
if { [lindex $commands $i] == "/system/reboot"} {
send "y\r"
}
expect {
-re "^\[^\n\r]*$reprompt" {}
-re "^\[^\n\r ]*>>.*$reprompt" { exp_continue }
-re "\[\n\r]+" { exp_continue }
}
}
if { $do_interact == 1 } {
interact
return 0
}
send "quit\r"
expect {
-re "^WARNING: There are unsaved configuration changes." {
send "y\r"
exp_continue
}
"\n" { exp_continue }
"\[^\n\r *]*Session terminated" { return 0 }
timeout { catch {close}; catch {wait};
return 0
}
eof { return 0 }
}
set in_proc 0
}
That's how it looks like
Does anyone have a solution?
I just find the solution in mtlogin at line 625!
foreach router [lrange $argv $i end] {
set router [string tolower $router]
send_user "$router\n"
# Figure out prompt.
set prompt "] > " #Just added a second whitespace after >
# alteon only "enables" based on the password used at login time
set autoenable 1
set enable 0
Hope it's gonna help you

Expect Script - Fixing weird terminal resizing behaviour

I wrote a small expect script to connect to multiple SSH Servers.
However if I interact with the terminal after initializing the connection, the terminal window behaves very odd.
For example, if I start the script in a non-fullsize terminal, and resize the window to make it larger after the Interact, it looks like that:
Now, I had this issue in my first expect script as well. But I was able to resolve it by adding the following code:
trap {
set XZ [stty rows ]
set YZ [stty columns]
stty rows $XZ columns $YZ < $spawn_out(slave,name)
} WINCH
And this worked perfectly fine! I was able to resize the terminal without a problem.
However, I added this piece of code to my new script, where there are multiple interact's in different proc's (functions). And on window resize, I get the following error:
can't read "spawn_out(slave,name)": no such variable
while executing
"stty rows $XZ columns $YZ < $spawn_out(slave,name)"
I have no idea on how to resolve this. Here is my code:
#!/usr/bin/expect -f
set SERVER "0"
set CHOICE "0"
set SERVER_1_PKEY [exec cat /home/me/p1]
set SERVER_2_PKEY [exec cat /home/me/p2]
set SERVER_1_HOST "server1.com"
set SERVER_2_HOST "server2.com"
set SERVER_1_USER "server1user"
set SERVER_2_USER "server2user"
set SERVER_1_PORT "22"
set SERVER_2_PORT "22"
trap {
set XZ [stty rows ]
set YZ [stty columns]
stty rows $XZ columns $YZ < $spawn_out(slave,name)
} WINCH
proc s1 {SERVER_1_PKEY SERVER_1_HOST SERVER_1_USER SERVER_1_PORT} {
send_user "\033c"
spawn ssh ${SERVER_1_USER}#${SERVER_1_HOST} -p ${SERVER_1_PORT}
expect "assword:"
send "${SERVER_1_PKEY}\r"
interact
}
proc s2 {} {
send_user "\033c"
spawn ssh ${SERVER_2_USER}#${SERVER_2_HOST} -p ${SERVER_2_PORT}
expect "assword:"
send "${SERVER_2_PKEY}\r"
interact
}
set arg [lindex $argv 0]
switch $arg {
"" { set CHOICE "0" }
"1" { set CHOICE "1" }
"2" { set CHOICE "2" }
}
if {$CHOICE eq "0"} {
puts -nonewline " Input \[1,2\]: "
flush stdout
gets stdin SERVER
if {$SERVER eq "1"} { s1 $SERVER_1_PKEY $SERVER_1_HOST $SERVER_1_USER $SERVER_1_PORT }
if {$SERVER eq "2"} { s2 $SERVER_2_PKEY $SERVER_2_HOST $SERVER_2_USER $SERVER_2_PORT }
}
if {$CHOICE eq "1"} { s1 $SERVER_1_PKEY $SERVER_1_HOST $SERVER_1_USER $SERVER_1_PORT }
if {$CHOICE eq "2"} { s2 $SERVER_2_PKEY $SERVER_2_HOST $SERVER_2_USER $SERVER_2_PORT }
Can anyone help me resolve this issue or tell me what I'm missing?
When you call spawn inside a procedure the array variable spawn_out(slave,name) has the scope of that procedure only. Usually, you can just make this into a global scope by declaring it as such inside each procedure:
proc s1 {...} {
global spawn_out
...
spawn ...
}
send_user $spawn_out(slave,name)

handling tcl expect application crash

Is it possible to spawn an application, send commands, expect results, but also 'respawn' the application in case it crashed and continue from the last point? I tried creating procedures for spawning, but I am not able to catch user shell prompt once the application gets closed
So it sounds like you are doing something like telneting or sshing
into a shell then running an application. The application dies or
hangs so the prompt does not return so expect does not return and so
you are stuck. You will need to use timeout to detect a hang and restart
your entire process ( including the spawn). Below is the boiler plate I start with when writing
expect scripts. The critical thing to help you resolve your problem
is to realize that spawn not only sets the spawn_id but also returns
the pid of the spawned process. You can use that pid to kill the
spawned process if you get an eof and/or a timeout. My boiler plate
kills the spawn process on timeout. The timeout does not bail from the
expect loop but waits for the eof before exiting. it also accumulates
the output of expect command so on timeout you may be able to see
where it dies. The boilerplate is in a proc called run. Spawn the process
and pass the pid and the spawn id . Reuse the boiler plate to define other
procs that will be the steps. put the procs in a script with a counter between
them as shown and repeat. The other answerer is correct that the unless the app
restarts where you left off you can need to start from scratch. If it does start from
where you left off. make the steps granular enough that you know what command
to repeat. and step to start from.
BoilerPlate
proc run { pid spawn_id buf } {
upvar $buf buffer; #buffer to accumulate output of expect
set bad 0;
set done 0;
exp_internal 0; # set to one for extensive debug
log_user 0; # set to one to watch action
expect {
-i $spawn_id
-re {} {
append buffer $expect_out(buffer); # accumultate expect output
exp_continue;
}
timeout {
send_user "timeout\n"
append buffer $expect_out(buffer); # accumultate expect output
exec kill -9 $pid
set bad 1
exp_continue;
}
fullbuffer {
send_user " buffer is full\n"
append buffer $expect_out(buffer); # accumultate expect output
exp_continue;
}
eof {
send_user "Eof detected\n"
append buffer $expect_out(buffer); # accumultate expect output
set done 1 ;
}
}
set exitstatus [ exp_wait -i $spawn_id ];
catch { exp_close -i $spawn_id };
if { $bad } {
if { $done } {
throw EXP_TIMEOUT "Application timeout"
}
throw BAD_ERROR "unexpected failure "
}
return $exitstatus
}
set count 0
set attempts 0 ; # try 4 times
while { $count == 0 && $attempts < 4 } {
set buff ""
set pid [spawn -noecho ssh user#host ]
try {
run $pid $::spawn_id buff
incr count
run2 $pid $::spawn_id buff
incr count
run3 $pid $::spawn_id buff
incr count
run4 $pid $::spawn_id buff
incr count
} trap EXP_TIMEOUT { a b } {
puts "$a $b"
puts " program failed at step $count"
} on error { a b } {
puts "$a $b"
puts " program failed at step $count"
} finally {
if { $count == 4 } {
puts "success"
} else {
set count 0
incr attempts
puts "$buff"
puts "restarting\n"
}
}
}

Pinging server with payload from tclsh

I need to test how my remote server is handling ping request. I need to ping remote server from my windows with payload of say 50 kb. I need my tcl script sholud generate 20 such ping requests with 50 kb payload parallel so it will result 1 mb receive traffic at server at given instance. here is the code for ping test
proc ping-igp {} {
foreach i {
172.35.122.18
} {
if {[catch {exec ping $i -n 1 -l 10000} result]} {
set result 0
}
if {[regexp "Reply from $i" $result]} {
puts "$i pinged"
} else {
puts "$i Failed"
}
}
}
If you want to ping in parallel then you can use open instead of exec and use fileevents to read from the ping process.
An example of using open to ping a server with two parallel processes:
set server 172.35.122.18
proc pingResult {chan serv i} {
set reply [read $chan]
if {[eof $chan]} {
close $chan
}
if {[regexp "Reply from $serv" $result]} {
puts "$serv number $i pinged"
} else {
puts "$serv number $i Failed"
}
}
for {set x 0} {$x < 2} {incr $x} {
set chan [open "|ping $server -n 1 -l 10000"]
fileevent $chan readable "pingResult $chan {$server} $x"
}
See this page for more info: http://www.tcl.tk/man/tcl/tutorial/Tcl26.html
Here's a very simple piece of code to do the pinging in the background by opening a pipeline. To do that, make the first character of the “filename” to open be a |, when the rest of the “filename” is interpreted as a Tcl list of command line arguments, just as in exec:
proc doPing {host} {
# These are the right sort of arguments to ping for OSX.
set f [open "|ping -c 1 -t 5 $host"]
fconfigure $f -blocking 0
fileevent $f readable "doRead $host $f"
}
proc doRead {host f} {
global replies
if {[gets $f line] >= 0} {
if {[regexp "Reply from $host" $result]} {
# Switch to drain mode so this pipe will get cleaned up
fileevent $f readable "doDrain $f"
lappend replies($host) 1
incr replies(*)
}
} elseif {[eof $f]} {
# Pipe closed, search term not present
lappend replies($host) 0
incr replies(*)
close $f
}
}
# Just reads and forgets lines until EOF
proc doDrain {f} {
gets $f
if {[eof $f]} {close $f}
}
You'll also need to run the event loop; this might be trivial (you're using Tk) or might need to be explicit (vwait) but can't be integrated into the above. But you can use a clever trick to run the event loop for long enough to collect all the results:
set hosts 172.35.122.18
set replies(*)
foreach host $hosts {
for {set i 0} {$i < 20} {incr i} {
doPing $host
incr expectedCount
}
}
while {$replies(*) < $expectedCount} {
vwait replies(*)
}
Then, just look at the contents of the replies array to get a summary of what happened.

How to have expect timeout when trying to login to an ssh session it has spawned?

I am writing an bash script that uses expect to login to a bunch of Cisco ASAs (they don't support certificate login, hence using expect), makes a change to the configuration and then logs out.
I'd like the script to move onto the next ASA if it is unable to login.
Here is the script:
#!/bin/bash
# Scriptname: set-mtu
for asa in $(cat asa-list-temp)
do
/usr/bin/expect << EndExpect
spawn ssh admin_15#$asa
expect "assword:"
send "pa$$w0rd\r"
expect ">"
send "do something\r"
expect ">"
send "exit\r"
EndExpect
done
I think I can set a timeout on expect "assword:" but I can't figure out how to get it to close the spawned ssh session and then move onto the next ASA in the for list.
First of all I would use an expect script for this and lose the bash scripting.
Then for the expect part:
You can do this by using a switch that also matches for timeout (page 12 of exploring expect). In that way you can explicitly have some action when expect timeouts.
Otherwise by setting the timeout it will just continue with the next command in line.
set timeout 60
expect {
"assword:" {
}
timeout {
exit 1 # to exit the expect part of the script
}
}
I've created something similar where I used an overall expect script to run an expect script in parallel.
multiple.exp
#!/bin/sh
# the next line restarts using tclsh \
exec expect "$0" "$#"
# multiple.exp --
#
# This file implements the running of multiple expect scripts in parallel.
# It has some settings that can be found in multiple.config
#
# Copyright (c) 2008
#
# Author: Sander van Knippenberg
#####
# Setting the variables
##
source [file dirname $argv0]/.multiple.config
# To determine how long the script runs
set timingInfo("MultipleProcesses") [clock seconds]
# ---------------------------------------------------------------------
######
# Procedure to open a file with a certain filename and retrieve the contents as a string
#
# Input: filename
# Output/Returns: content of the file
##
proc openFile {fileName} {
if {[file exists $fileName] } {
set input [open $fileName r]
} else {
puts stderr "fileToList cannot open $fileName"
exit 1
}
set contents [read $input]
close $input
return $contents
}
######
# Procedure to write text to a file with the given filename
#
# Input: string, filename
##
proc toFile {text filename} {
# Open the filename for writing
set fileId [open $filename "w"]
# Send the text to the file.
# Failure to add '-nonewline' will reslt in an extra newline at the end of the file.
puts -nonewline $fileId $text
# Close the file, ensuring the data is written out before continueing with processing
close $fileId
}
# ---------------------------------------------------------------------
# Check for the right argument
if {$argc > 0 } {
set hostfile [lindex $argv 0]
} else {
puts stderr "$argv0 --- usage: $argv0 <hosts file>"
exit 1
}
# Create the commands that can be spawned in parallel
set commands {}
# Open the file with devices
set hosts [split [openFile $hostfile] "\n"]
foreach host $hosts {
if { [string length $host] > 1 } {
lappend commands "$commandDir/$commandName $host" # Here you can enter your own command!
}
}
# Run the processes in parallel
set idlist {}
set runningcount 0
set pattern "This will never match I guess"
# Startup the first round of processes until maxSpawn is reached,
# or the commands list is empty.
while { [llength $idlist] < $maxSpawn && [llength $commands] > 0} {
set command [lindex $commands 0]
eval spawn $command
lappend idlist $spawn_id
set commands [lreplace $commands 0 0]
incr runningcount
set commandInfo($spawn_id) $command
set timingInfo($spawn_id) [clock seconds]
send_user " $commandInfo($spawn_id) - started\n"
}
# Finally start running the processes
while {$runningcount > 0} {
expect {
-i $idlist $pattern {
}
eof {
set endedID $expect_out(spawn_id)
set donepos [lsearch $idlist $endedID]
set idlist [lreplace $idlist $donepos $donepos]
incr runningcount -1
set elapsedTime [clock format [expr [clock seconds] - $timingInfo($endedID)] -format "%M:%S (MM:SS)"]
send_user " $commandInfo($endedID) - finished in: $elapsedTime\n"
# If there are more commands to execute then do it!
if {[llength $commands] > 0} {
set command [lindex $commands 0]
eval spawn $command
lappend idlist $spawn_id
set commands [lreplace $commands 0 0]
incr runningcount
set commandInfo($spawn_id) $command
set timingInfo($spawn_id) [clock seconds]
}
}
timeout {
break
}
}
}
set elapsed_time [clock format [expr [clock seconds] - $timingInfo("MultipleProcesses")] -format "%M:%S (MM:SS)"]
send_user "$argv0 $argc - finished in: $elapsedTime\n"
multiple.config
# The dir from where the commands are executed.
set commandDir "/home/username/scripts/expect/";
set commandName "somecommand.exp";
# The maximum number of simultanious spawned processes.
set maxSpawn 40;
# The maximum timeout in seconds before any of the processes should be finished in minutes
set timeout 20800;
To make the answer clear: the solution is typical, just make timeout treatment inside the curly braced expect notation. So, your Tcl/Expect part in the shell script should be:
spawn ssh user#host
expect {
"assword:" {
send "password\r"
}
timeout {
exit
}
}
expect "prompt>"
. . .
login success
. . .
Here is another example, how to expect/treat timeout to resume waiting for the result string while the spawned command is still running.

Resources