Can anybody take a look and advice why the following snippet does not execute neither branch of the if statement? Script does not throw any errors. It simply reaches the expect line and waits until it times out.
arg1 is just an argument from command line.
set arg1 [lindex $argv 0]
set strname "teststring"
expect {
"prompt#" {
if { [string compare $arg1 $strname] != 0 } {
send "strings different\r"
} else {
send "strings same\r"
}
}
default abort
}
Thanks in advance.
EDIT:
Edited to show the correct structure.
Expect is an extension of Tcl, and Tcl is very particular about whitespace:
...
# need a space between the end of the condition and the beginning of the true block
if { [string compare $arg1 $strname] != 0 } {
...
# the else keyword needs to be on the same line as the closing bracket of the true block
} else {
...
}
Is your complete script more like this?:
#!/usr/bin/expect -d
set arg1 [lindex $argv 0]
set strname teststring
expect {
"prompt#"
if { [string compare $arg1 $strname] != 0 }{
send "strings different\r";
}
else{
send "strings same\r";
}
default abort
}
Or are you running expect some other way? How exactly are you running expect, your script, with what args?
Related
I'm modifying the code below, but I have no idea how it works - enlightenment welcome. The issue is that there is a proc in it (cygwin_prefix) which is meant to create a command, by either
leaving a filename unmodified, or
prepending a string to the filename
The problem is that the proc returns nothing, but the script magically still works. How? Specifically, how does the line set command [cygwin_prefix filter_g] actually manage to correctly set command?
For background, the script simply execs filter_g < foo.txt > foo.txt.temp. However, historically (this no longer seems to be the case) this didn't work on Cygwin, so it instead ran /usr/bin/env tclsh filter_g < foo.txt > foo.txt.temp. The script as shown 'works' on both Linux (Tcl 8.5) and Cygwin (Tcl 8.6).
Thanks.
#!/usr/bin/env tclsh
proc cygwin_prefix { file } {
global cygwin
if {$cygwin} {
set status [catch { set fpath [eval exec which $file] } result ]
if { $status != 0 } {
puts "which error: '$result'"
exit 1
}
set file "/usr/bin/env tclsh $fpath"
}
set file
}
set cygwin 1
set filein foo.txt
set command [cygwin_prefix filter_g]
set command "$command < $filein > $filein.temp"
set status [catch { eval exec $command } result ]
if { $status != 0 } {
puts "filter error: '$result'"
exit 1
}
exit 0
The key to your question is two-fold.
If a procedure doesn't finish with return (or error, of course) the result of the procedure is the result of the last command executed in that procedure's body.
(Or the empty string, if no commands were executed. Doesn't apply in this case.)
This is useful for things like procedures that just wrap commands:
proc randomPick {list} {
lindex $list [expr { int(rand() * [llength $list]) }]
}
Yes, you could add in return […] but it just adds clutter for something so short.
The set command, with one argument, reads the named variable and produces the value inside the var as its result.
A very long time ago (around 30 years now) this was how all variables were read. Fortunately for us, the $… syntax was added which is much more convenient in 99.99% of all cases. The only place left where it's sometimes sensible is with computed variable names, but most of the time there's a better option even there too.
The form you see with set file at the end of a procedure instead of return $file had currency for a while because it produced slightly shorter bytecode. By one unreachable opcode. The difference in bytecode is gone now. There's also no performance difference, and never was (especially by comparison with the weight of exec which launches subprocesses and generally does a lot of system calls!)
It's not required to use eval for exec. Building up a command as a list will protect you from, for example, path items that contain a space. Here's a quick rewrite to demonstrate:
proc cygwin_prefix { file } {
if {$::cygwin} {
set status [catch { set fpath [exec which $file] } result]
if { $status != 0 } {
error "which error: '$result'"
}
set file [list /usr/bin/env tclsh $fpath]
}
return $file
}
set cygwin 1
set filein foo.txt
set command [cygwin_prefix filter_g]
lappend command "<" $filein ">" $filein.temp
set status [catch { exec {*}$command } result]
if { $status != 0 } {
error "filter error: '$result'"
}
This uses {*} to explode the list into individual words to pass to exec.
I am using the following TCL code:
proc RunCSM { scen } {
catch { $scen start }
if { "[$scen status]" != "SUCCESS" } {
puts "$scen FAILED. Error Info:"
puts "[$scen errorInfo]" ...
I need also the line number of the code that fails. In 8.5 and onwards this is achieved by this nice solution
How can I get the code line number along with errorinfo?
How is it possible to achieve the same but in version 8.4?
The easiest approach is to parse the errorInfo variable. Here's what an example looks like:
% parray foo
"foo" isn't an array
% set errorInfo
"foo" isn't an array
while executing
"error "\"$a\" isn't an array""
(procedure "parray" line 4)
invoked from within
"parray foo"
Parsing that with regexp isn't too hard, provided we use the -line option.
proc getLineFromErrorInfo {} {
global errorInfo
if {[regexp -line { line (\d+)\)$} $errorInfo -> line]} {
return $line
} else {
# No guarantee that there's information there...
return "unknown"
}
}
On our example from before, we can then do:
getLineFromErrorInfo
and it will return 4. You might want to extend the RE to also capture the name of the procedure; line numbers in 8.4 and before are always relative to their procedure. (This is also mostly true in 8.5 onwards; this is an area where backward compatibility is a bit painful IMO.) Here's how you might do that:
proc getLocusFromErrorInfo {} {
global errorInfo
if {[regexp -line {\(procedure "(.*?)" line (\d+)\)$} $errorInfo -> proc line]} {
return [list $proc $line]
} else {
# No guarantee that there's information there...
return "unknown"
}
}
Note that merely knowing where the error came from doesn't necessarily tell you where the problem is, especially in production code, since it could be due to bad arguments elsewhere that have been passed around a bit…
bind pub - "!find" pub:cari
proc pub:cari { nick host hand chan text } {
set judul [lindex $text 0]
if { $judul == ""} {
puthelp "notice $nick :ketik !find <penyanyi/artis>"
return
} else {
putquick "notice $nick :being processed $judul"
catch [list exec find /home/gusman/mp3 -name "*$judul*" -type f -printf "%f\n"] data
putserv "notice $nick :!putar $data"
putserv "notice $nick :copy paste di channel !putar $data"
}
}
putlog "find.tcl"
if sought is in the data
it works well to post to the target.
if the searched in data is empty, it does not work, please indicate whether this scripts is incomplete or wrong.
I've got the solution on this line:
if { $judul == ""}
Does something like this work?
proc pub:cari { args } {
lassign $args nick host hand chan text
...
Normally in TCL if you don't pass enough arguments to a function then you'll get an error. If you don't know how many arguments your function will take, you can name the last argument "args", and any extra arguments will be put in this variable as a list.
I'm relatively new in TCL, in TCL prompt, when we invoke a proc with some return value, the proc's return value is echoed back by tcl. Is there a way to stop it (without affecting puts or similar functionality) as an example
bash$ tclsh
% proc a {} { puts "hello"; return 34; }
% a
hello
34
%
Now how do i suppress the 34 coming to the screen? Any help is appreciated.
Update:
Actually the proc is a part of another tool, earlier it did not have any return value, but now conditionally it can return a value.
it can be called from a script and there won't be any problem (as Bryan pointed out). and it can be called from interactive prompt, then after all the necessary outputs, the return value is getting printed unnecessarily.
So 1) I don't have the facility of changing a user's tclshrc 2) existing scripts should continue to work.
And it seems strange that every time the proc is called, after all the necessary outputs, a number gets printed. To a user, this is a needless information unless he has caught the value and wants to do something. So i wanted the value to be delivered to user, but without getting printed to prompt/UI (hope i'm clear )
The interactive shell code in tclsh and wish will print any non-empty result. To get nothing printed, you have to have the last command on the “line” produce an empty result. But which command to use?
Many commands will produce an empty result:
if 1 {}
subst ""
format ""
However, the shortest is probably:
list
Thus, you could write your code like:
a;list
Of course, this only really becomes useful when your command actually produces a large result that you don't want to see. In those cases, I often find that it is most useful to use something that measures the size of the result, such as:
set tmp [something_which_produces a_gigantic result]; string length $tmp
The most useful commands I find for that are string length, llength and dict size.
If you absolutely must not print the result of the command, you have to write your own interactive loop. There are two ways to do this, depending on whether you are running inside the event loop or not:
Without the event loop
This simplistic version just checks to see if the command name is in what the user typed. It's probably not a good idea to arbitrarily throw away results otherwise!
set accum ""
while {[gets stdin line] >= 0} {
append accum $line "\n"
if {[info complete $accum]} {
if {[catch $accum msg]} {
puts stderr $msg
} elseif {$msg ne "" && ![string match *TheSpecialCommand* $accum]} {
puts $msg
}
set accum ""
}
}
With the event loop
This is just handling the blocking IO case; that's the correct thing when input is from a cooked terminal (i.e., the default)
fileevent stdin readable handleInput
set accum ""
proc handleInput {} {
global accum
if {[gets stdin line] < 0} {
exit; # Or whatever
}
append accum $line "\n"
if {[info complete $accum]} {
if {[catch {uplevel "#0" $accum} msg]} {
puts stderr $msg
} elseif {$msg ne "" && ![string match *TheSpecialCommand* $accum]} {
puts $msg
}
set accum ""
}
}
vwait forever; # Assuming you're not in wish or have some other event loop...
How to detect the command is being executed
The code above uses ![string match *TheSpecialCommand* $accum] to decide whether to throw away the command results, but this is very ugly. A more elegant approach that leverages Tcl's own built-in hooks is to use an execution trace to detect whether the command has been called (I'll just show the non-event-loop version here, for brevity). The other advantage of this is that it is simple to extend to suppressing the output from multiple commands: just add the trace to each of them.
trace add execution TheSpecialCommand enter SuppressOutput
proc SuppressOutput args {
# Important; do not suppress when it is called inside another command
if {[info level] == 1} {
set ::SuppressTheOutput 1
}
}
# Mostly very similar from here on
set accum ""
while {[gets stdin line] >= 0} {
append accum $line "\n"
if {[info complete $accum]} {
set SuppressTheOutput 0; # <<<<<< Note this!
if {[catch $accum msg]} {
puts stderr $msg
} elseif {$msg ne "" && !$SuppressTheOutput} { # <<<<<< Note this!
puts $msg
}
set accum ""
}
}
To be clear, I wouldn't ever do this in my own code! I'd just suppress the output manually if it mattered.
You could make an empty procedure in .tclshrc...
proc void {} {}
...and when you don't need a return value, end the line with ;void.
Use tcl_interactive variable to enable the return of of the value, although I'm not sure where this would be useful...
proc a {} {
puts "hello"
if { [info exist tcl_interactive] } {
return {};
} else {
return 34;
}
}
I am a total expect noob.
I am writing a expect script for a test case where I want to count the number of occurrences of the string "Ok" and do an action for every occurrence from the following output:
Reloading configuration on all nodes
Reloading configuration on node 1 (node1-c5)
OK
Reloading configuration on node 2 (node2-c5)
OK
Reloading configuration on node 3 (node3-c5)
OK
Reloading configuration on node 4 (node4-c5)
OK
How would my expect block look like?
I'd rewrite your code to remove the while loop:
set number_ok 0
set node_patt "(Reloading configuration on node (\\d+?) \\((\[^)]+?)\\))\r\n(.+?)\r\n"
send "cluster config -r -a\r"
expect {
ERROR {cluster_exit 1}
-re $node_patt {
set line "<$cmdline> $expect_out(1,string)"
set node_num $expect_out(2,string)
set node_name $expect_out(3,string)
set result $expect_out(4,string)
switch -regexp $result {
"Node .+ not found" {
ok 0 "$line (not found)"
}
"Node .+ not responding, skipped" {
ok 0 "$line (not responding)"
}
OK {
ok 1 $line
incr number_ok
}
}
exp_continue ;# loop back to top of expect block
}
$ROOT_PROMPT ;# no action, so fall out of the expect block
}
Note that Tcl regular expressions are either entirely greedy or entirely non-greedy. I use \r\n(.+)\r\n to capture the line following "Reloading configuration on node ...". However the .+ part must not contain newlines, so it has to be non-greedy. Thus, every quantifier in node_patt has to be non-greedy.
The code ended up looking like this (a simple loop):
send "cluster config -r -a \r"
set done 0
set number_ok 0
while {$done == 0} {
set done 1
expect {
$ROOT_PROMPT { set done 1 }
"ERROR" { cluster_exit 1 }
-re "Reloading configuration on node.*\r" {
set line "<$cmdline> $expect_out(0,string)"
expect {
$ROOT_PROMPT { set done 1 }
"ERROR" { cluster_exit 1 }
-re "Node * not found" { ok 0 "$line (not found)" }
-re "Node * not responding, skipped" { ok 0 "$line (not responding)" }
"OK" {
ok 1 "$line"
set number_ok [expr $number_ok + 1]
set done 0
}
}
}
}
}
diag "Done $done"
diag "Count $number_ok"