Im trying to learn Expect scripting to run backend process, is there a way to raise and catch an error exception from this language?
Ex. Python
try:
raise Exception("test")
except Exception, e:
print e
What's the equivalent in expect?
#!/usr/bin/expect
package require Expect
# raise and catch exception
If you're using Tcl 8.6 (which the Expect package will load into nicely), the most literal translation of that Python code is:
try {
throw Exception "test"
} trap Exception e {
puts $e
}
Now, the try and throw commands were added in 8.6 to make this sort of thing easier. Prior to that (all the way from far further back than I can search conveniently) you would instead do something like this:
if {[catch {
error "test"
} e] == 1} {
puts $e
}
Which is easy enough in this case, but rather more error-prone once things get more complex.
In TCL, you can use catch to catch an exception:
if {[catch { package require Expect } errmsg]} {
puts "Import failed with message: $errmsg"
} else {
puts "Import succeeded"
}
To throw an exception, use the return -code error command. For example:
proc double {x} {
if {![string is integer $x]} {
return -code error "double expects an int, not '$x'"
}
return [expr {$x * 2}]
}
set x 5
puts "x = $x"
puts "2x = [double $x]"
set x "Hello"
puts "x = $x"
if {[catch {puts "2x = [double $x]"} errmsg]} {
puts "Error: $errmsg"
}
Output:
x = 5
2x = 10
x = Hello
Error: double expects an int, not 'Hello'
The A literal translation of your python example is
set status [catch {error "test"} e]
if {$status} {
puts $e
}
This is Tcl 8.5
Related
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…
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]" ...
The problem is that in this case that there is an error, it shows the error info debug information as desired as the output of errorInfo flag, but in this case I need also the line number of the code that fails. How is this possible?
The easiest way, from 8.5 onwards, is to switch to the form of catch that lets you get the extended result information as a dictionary (in the opt variable below) in its second optional argument after the script:
catch { $scen start } msg opt
if { "[$scen status]" ne "SUCCESS" } { # use 'ne' to compare strings, please
set info [dict get $opt -errorinfo]
set line [dict get $opt -errorline]
puts "$scen FAILED saying '$msg' at $line. Error Info:"
puts $info
# ...
}
When I run this the below TCL code, I get the successful output for first service call ie.
"Result = xyz"
However, the second service call executes the service and the service returns an error, because of that we enter in the if loop however it doesnt print the error message returned by service call and it just prints an empty string.
proc serviceCall {prefix} {
return [mimic.list :prefix $prefix]
}
puts "###### Svc Call With prefix set to Single Char ################## \n"
if {[catch {puts "Result = [serviceCall \"l\"]"} errmsg]} {
puts "ErrorMsg: $errmsg\n"
}
puts "\n\n###### Svc Call With prefix set to space ################## \n"
if {[catch {puts "Result = [serviceCall \"\ \" ]"} errmsg]} {
puts "ErrorMsg: $errmsg\n"
}
You might want to use try instead of catch, and use less backslashes (unless you indend to send a 3 character string with literal quotes to the proc)
puts "###### Svc Call With prefix set to Single Char ################## \n"
try {
puts "Result = [serviceCall "l"]"
} on error errmsg {
puts "ErrorMsg: $errmsg"
}
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;
}
}
What is actually the difference between raising an exception in TCL via return -code error ...and error ...? When would one be used instead of the other?
The error command produces an error right at the current point; it's great for the cases where you're throwing a problem due to a procedure's internal state.
The return -code error command makes the procedure it is placed in produce an error (as if the procedure was error); it's great for the case where there's a problem with the arguments passed to the procedure (i.e., the caller did something wrong).
The difference really comes when you look at the stack trace.
Here's a (contrived!) example:
proc getNumberFromFile {filename} {
if {![file readable $filename]} {
return -code error "could not read $filename"
}
set f [open $filename]
set content [read $f]
close $f
if {![regexp -- {-?\d+} $content number]} {
error "no number present in $filename"
}
return $number
}
catch {getNumberFromFile no.such.file}
puts $::errorInfo
#could not read no.such.file
# while executing
#"getNumberFromFile no.such.file"
catch {getNumberFromFile /dev/null}
puts $::errorInfo
#no number present in /dev/null
# while executing
#"error "no number present in $filename""
# (procedure "getNumberFromFile" line 9)
# invoked from within
#"getNumberFromFile /dev/null"