I'm attempting to access a variable within a while loop in expect, but I keep getting an error that the variable doesn't exist. The code in question:
#!/usr/bin/expect
spawn ./simulator.sh
set timeout 1
...<extra code>...
# Return the time in seconds from epoch
proc getTime {year month day hour minute} {
set rfc_form ${year}-${month}-${day}T${hour}:${minute}
set time [clock scan $rfc_form]
return $time
}
# Get a handle to the file to store the log output
set fileId [open $filename "a"]
# Get one line at a time
expect -re {.*the number of lines from the debug log file to appear on one page.*}
send "1\r"
# Get the initial time stamp and store the first line
expect -re {^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})\..*$} {
puts -nonewline $fileId $expect_out(0,string)
set initTime $expect_out(1,string) $expect_out(2,string) $expect_out(3,string) $expect_out(4,string) $expect_out(5,string)
}
send "1\r"
# Iterate over the logs until we get at least 5 minutes worth of log data
while { true } {
expect -re {^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})\..*$} {
puts -nonewline $fileId $expect_out(0,string)
set currentTime $expect_out(1,string) $expect_out(2,string) $expect_out(3,string) $expect_out(4,string) $expect_out(5,string)
}
# 300 = 5 minutes * 60 seconds
if { $initTime - $currentTime > 300 } break
expect -re {.*enter.*}
send "n\r"
}
...<extra code>...
And the error I get is:
$ ./test.sh
the number of lines from the debug log file to appear on one page1
201505151624.00 721660706 ns | :OCA (027):MAIN (00) | CONTROL |START LINE
enter1
201505151620.00 022625203 ns | :OCA (027):MAIN (00) | CONTROL |com.citrix.cg.task.handlers.ADDeltaSyncHandler:ThreadID:1182, Delta sync on:activedirectory2
entercan't read "initTime": no such variable
while executing
"if { $initTime - $currentTime > 300 } break"
("while" body line 7)
invoked from within
"while { true } {
expect -re {^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})\..*$} {
puts -nonewline $fileId $expect_out(0,string)
set currentTime $expect_o..."
(file "./test.sh" line 42)
I'm sure I'm doing something incredibly stupid, but I can't figure it out for the life of me. Thanks for the help in advance!
This expect pattern has not matched:
expect -re {^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})\..*$} {
puts -nonewline $fileId $expect_out(0,string)
set initTime $expect_out(1,string) $expect_out(2,string) $expect_out(3,string) $expect_out(4,string) $expect_out(5,string)
}
You would know if it did match because you would get a syntax error: the set command takes only one or two arguments, and you have given it 6.
Add exp_internal 1 to a line before the spawn command, and expect will show you verbose debugging to let you know how your patterns are or are not matching.
To solve the set syntax error, you probably want
set initTime [getTime $expect_out(1,string) $expect_out(2,string) $expect_out(3,string) $expect_out(4,string) $expect_out(5,string)]
Also, in the if condition, be aware that $initTime will be smaller than $currentTime , so {$initTime - $currentTime > 300} will never be true, so your loop will be infinite.
Related
Currently, I am working on a script to automatize a Data Collector process. Through a long term run i have split these Collector script into four pieces. Now I want to start these Collectorscripts simultan, but i dont know how to do this. my Code ist working a bit:
package require Expect
log_user 0
set timeout 10200
spawn ./Log.tcl 2 5 1; set spawn1 $spawn_id
spawn ./Log.tcl 3 4 2; set spawn2 $spawn_id
spawn ./Log.tcl 7 8 2; set spawn3 $spawn_id
spawn ./Log.tcl 6 9 3; set spawn4 $spawn_id
expect -i $spawn1 eof {wait ; puts "--- 2,5 fertig ---"}
expect -i $spawn2 eof {wait ; puts "--- 3,4 fertig ---"}
expect -i $spawn3 eof {wait ; puts "--- 7,8 fertig ---"}
expect -i $spawn4 eof {puts "--- 6,9 fertig ---"}
this is run and made the thing. But if one job is ready before the other it will produces zombies. Is there a possibility to made this easy and beautiful? I have try a few thinks with exp_after, exp_background, $any_spawn_id with a while loop. But nothing worked. expect never get eof.
If you need to know the order in which the spawned commands finished, it is difficult to do with wait, but expect will accept a list of spawn ids to listen to simultaneously. For example,
spawn sleep 2
lappend allids $spawn_id
set cmd($spawn_id) "sleep 2"
spawn sleep 1
lappend allids $spawn_id
set cmd($spawn_id) "sleep 1"
while { [llength $allids]>0 } {
expect -i "$allids" eof {
puts "eof on $expect_out(spawn_id) from cmd $cmd($expect_out(spawn_id))"
set idx [lsearch -exact $allids $expect_out(spawn_id)]
set allids [lreplace $allids $idx $idx]
}
}
This runs 2 commands, sleep 2 and sleep 1, and appends each spawn id into the list allids. For convenience, the command is also noted in the array cmd indexed by the spawn id of that command.
The list of all spawn ids is then given to expect eof using -i.
When an eof is matched, the global variable $expect_out(spawn_id) contains the spawn id of the process causing the match. A message is printed after indexing this value in the cmd array.
Finally, the spawn id with eof is removed from the list, and the loop repeated until the list is empty.
Note, you cannot use exp_continue to continue the expect loop, as the -i value does not seem to be re-evaluated.
I have expect (tcl) script for automated task working properly - configuring network devices via telnet/ssh. Most of the cases there is 1,2 or 3 command lines to execute, BUT now I have more then 100 command lines to send via expect. How can I achieved this in smart and good scripting way :)
Because I can join all command lines over 100 to a variable "commandAll" with "\n" and "send" them one after another, but I think it's pretty ugly :) Is there a way without stacking them together to be readable in code or external file ?
#!/usr/bin/expect -f
set timeout 20
set ip_address "[lrange $argv 0 0]"
set hostname "[lrange $argv 1 1]"
set is_ok ""
# Commands
set command1 "configure snmp info 1"
set command2 "configure ntp info 2"
set command3 "configure cdp info 3"
#... more then 100 dif commands like this !
#... more then 100 dif commands like this !
#... more then 100 dif commands like this !
spawn telnet $ip_address
# login & Password & Get enable prompt
#-- snnipped--#
# Commands execution
# command1
expect "$enableprompt" { send "$command1\r# endCmd1\r" ; set is_ok "command1" }
if {$is_ok != "command1"} {
send_user "\n### 9 Exit before executing command1\n" ; exit
}
# command2
expect "#endCmd1" { send "$command2\r# endCmd2\r" ; set is_ok "command2" }
if {$is_ok != "command2"} {
send_user "\n### 9 Exit before executing command2\n" ; exit
}
# command3
expect "#endCmd2" { send "$command3\r\r\r# endCmd3\r" ; set is_ok "command3" }
if {$is_ok != "command3"} {
send_user "\n### 9 Exit before executing command3\n" ; exit
}
p.s. I'm using one approach for cheeking is given cmd line is executed successfully but I'm not certain that is perfect way :D
don't use numbered variables, use a list
set commands {
"configure snmp info 1"
"configure ntp info 2"
"configure cdp info 3"
...
}
If the commands are already in a file, you can read them into a list:
set fh [open commands.file]
set commands [split [read $fh] \n]
close $fh
Then, iterate over them:
expect $prompt
set n 0
foreach cmd $commands {
send "$cmd\r"
expect {
"some error string" {
send_user "command failed: ($n) $cmd"
exit 1
}
timeout {
send_user "command timed out: ($n) $cmd"
exit 1
}
$prompt
}
incr n
}
While yes, you can send long sequences of commands that way, it's usually a bad idea as it makes the overall script very brittle; if anything unexpected happens, the script just keeps on forcing the rest of the script over. Instead, it is better to have a sequence of sends interspersed with expects to check that what you've sent has been accepted. The only real case for sending a very long string over is when you're creating a function or file on the other side that will act as a subprogram that you call; in that case, there's no really meaningful place to stop and check for a prompt half way. But that's the exception.
Note that you can expect two things at once; that's often very helpful as it lets you check for errors directly. I mention this because it is a technique often neglected, yet it allows you to make your script far more robust.
...
send "please run step 41\r"
expect {
-re {error: (.*)} {
puts stderr "a problem happened: $expect_out(1,string)"
exit 1
}
"# " {
# Got the prompt; continue with the next step below
}
}
send "please run step 42\n"
...
I am trying to get my expect script to read a file, run a command for every line of the file and exit saving the log. The code is below:
#!/usr/bin/expect -f
set user [lrange $argv 0 0]
set password [lrange $argv 1 1]
set ipaddr [lrange $argv 2 2]
set arg1 [lrange $argv 3 3]
set systemTime [clock seconds]
set time [clock format $systemTime -format %a_%b_%d_%Y#%H'%M'%S]
set fp [open "$arg1" r]
set a "ssh-"
set b ".txt"
set s "_"
append newfile "${a}${arg1}${s}${time}${b}"
set timeout -1
spawn ssh $user#$ipaddr
match_max 100000
expect "*?assword:*"
send -- "$password\r"
log_file "$newfile" ;
expect "*#"
send_user "This is the $argv0 Script\r"
while {[gets $fp line] != -1} {
send -- "scm $line\r"
expect "*#"
}
close
send -- "exit\r"
expect eof
My problem is that once it comes to the end of the file i get the following error:
E6000_Lab_1# send: spawn id exp7 not open
while executing
"send -- "exit\r""
(file "filetest.tcl" line 28)
Can anyone help me get rid of this error please?
Sorry for doing this but it seems that I got the asnwer myself once again.
Thanks very much for all of you who answer and provide some ideas to the resolution of these issues.
The solution to my problem was on the id of the file that had been opened. Once I closed that, my code stopped crashing, the snipet is below:
while {[gets $fp line] != -1} {
send -- "scm $line\r"
expect "*#"
}
close $fp
send "ping xxx.xxx.xxx.xxx timeout 1 repeat-count 100\r"
expect "# "
send -- "exit\r"
expect eof
As you can see, the "$fp" after the "close" argument allows me to send the next command out of the loop and without errors.
You can't do either send or expect after you close the connection to the subprocess.
Say I have a variable that is set to some user input. I have no control over what the user will enter.
How would I go about removing all characters that are not in [A-Za-z0-9], spaces, periods, or commas?
proc getUserInput {} {
set timeout 60
send_user "\nEnter user input: "
expect_user {
-re "(.*)\n" {
set userInput $expect_out(1,string)
}
timeout {
exitTimeout "Timed out waiting for user input!"
}
}
return $userInput
}
set rawValue [ getUserInput ]
// massage variable goes here?
set massagedValue "$rawValue"
Not sure if it matters, but I'm using expect 5.45.
$ expect -v
expect version 5.45
Expect is a Tcl extension so you can use all Tcl commands when writing Expect scripts. You can try this in tclsh:
% set v1 "###the string###"
###the string###
% set v2 [regsub -all {[^ .,[:alnum:]]} $v1 ""]
the string
%
I need some help with an EXPECT script please....
I'm trying to automate a login, prior to accessing a load of hosts, and cater for when a user enters a password incorrectly. I am getting the username and password first, and then validating this against a particular host. If the password is invalid, I want to loop round and ask for the username and password again.
I am trying this :-
(preceding few irrelevant lines omitted)
while {1} {
send_user "login as:- "
expect -re "(.*)\n"
send_user "\n"
set user $expect_out(1,string)
stty -echo
send_user "password: "
expect -re "(.*)\n"
set password $expect_out(1,string)
stty echo
set host "some-box.here.there.co.uk"
set hostname "some-box"
set host_unknown 0
spawn ssh $user#$host
while {1} {
expect {
"Password:" {send $password\n
break}
"(yes/no)?" {send "yes\n"}
"Name or service not known" {set host_unknown 1
break}
}
}
if {$host_unknown < 1} {
expect {
"$hostname#" {send "exit\r"
break
}
"Password:" {send \003
expect eof
close $spawn_id
puts "Invalid Username or Password - try again..."
}
}
} elseif {$host_unknown > 0} {
exit 0}
}
puts "dropped out of loop"
And now I can go off and do lots of stuff to lots of boxes .....
This works fine when I enter a valid username or password, and my script goes off and does all the other stuff I want, but when I enter an invalid password I get this :-
Fred#Aserver:~$ ./Ex_Test.sh ALL
login as:- MyID
password: spawn ssh MyID#some-box.here.there.co.uk
Password:
Password:
Invalid Username or Password - try again...
login as:- cannot find channel named "exp6"
while executing "expect -re "(.*)\n""
invoked from within "if {[lindex $argv 1] != ""} {
puts "Too many arguments"
puts "Usage is:- Ex_Test.sh host|ALL"
} elseif {[lindex $argv 0] != ""} {
while {1} {
..."
(file "./Ex_Test.sh" line 3)
Its the line "can not find channel named "exp6" which is really bugging me.
What am I doing wrong? I am reading Exploring Expect (Don Lines) but getting nowhere....
Whenever expect is supposed to wait for some word, it will save the spawn_id for that expect process into expect_out(spawn_id).
As per your code, expect's spawn_id is generated when it encounters
expect -re "(.*)\n"
When user typed something and pressed enter key, it will save the expect's spawn_id. If you have used expect with debugging, you might have seen the following in the debugging output
expect does "" (spawn_id exp0) match regular expression "(.*)\n"
Lets say user typed 'Simon', then the debugging output will be
expect: does "Simon\n" (spawn_id exp0) match regular expression "(.*)\n"? Gate "*\n"? gate=yes re=yes
expect: set expect_out(0,string) "Simon\n"
expect: set expect_out(1,string) "Simon"
expect: set expect_out(spawn_id) "exp0"
expect: set expect_out(buffer) "Simon\n"
As you can see, the expect_out(spawn_id) holds the spawn_id from which it has to expect for values. In this case, the term exp0 pointing the standard input.
If spawn command is used, then as you know, the tcl variable spawn_id holds the reference to the process handle which is known as the spawn handle. We can play around with spawn_id by explicitly setting the process handle and save it for future reference. This is one good part.
As per your code, you are closing the ssh connection when wrong password given with the following code
close $spawn_id
By taking advantage of spawn_id, you are doing this and what you are missing is that setting the expect's process handle back to it's original reference handle. i.e.
While {1} {
###Initial state. Nothing present in spawn_id variable ######
expect "something here"; #### Now exp0 will be created
###some code here ####
##Spawning a process now###
spawn ssh xyz ##At this moment, spawn_id updated
###doing some operations###
###closing ssh with some conditions###
close $spawn_id
##Loop is about to end and still spawn_id has the reference to ssh process
###If anything present in that, expect will assume that might be current process
###so, it will try to expect from that process
}
When the loop executes for the 2nd time, expect will try to expect commands from the spawn_id handle which is nothing but ssh process which is why you are getting the error
can not find channel named "exp6"
Note that the "exp6" is nothing but the spawn handle for the ssh process.
Update :
If some process handle is available in the
spawn_id, then expect will always expect commands from that
process only.
Perhaps you can try something like the following to avoid these.
#Some reference variable
set expect_init_spawn_id 0
while {1} {
if { $expect_spawn_id !=0 } {
#when the loop enters from 2nd iteration,
#spawn_id is explicitly set to initial 'exp0' handle
set spawn_id $expect_init_spawn_id
}
expect -re "(.*)\n"
#Saving the init spawn id of expect process
#And it will have the value as 'exp0'
set expect_init_spawn_id $expect_out(spawn_id)
spawn ssh xyz
##Manipulations here
#closing ssh now
close $spawn_id
}
This is my opinion and it may not be the efficient approach. You can also think of your own logic to handle these problems.
You simply need to store the $spawn_id as a temp variable before a nested expect command, then set the $spawn_id to the temp variable after a nested expect command.
Also, get rid of the while {1} loops. They are not needed because expect behaves like a loop provided you use exp_continue whenever you don't wish to exit. You don't need expect eof nor do you need close $spawn_id. I don't use them in the following example:
#!/usr/bin/expect
set domain [lindex $argv 0];
set timeout 300
spawn ./certbot-add.sh $domain
expect {
"*replace the certificate*" {
send "2\r";
exp_continue;
}
"*_acme-challenge*" {
puts [open output.txt w] $expect_out(buffer)
spawn ./acme-add.sh $domain
set tmp_spawn_id $spawn_id
expect {
"$ "
}
set spawn_id $tmp_spawn_id
send "\r";
exp_continue;
}
"*certificate expires on*" {
puts "Certificate Added!"
}
}