prevent call stack to be outputted - tcl

Is it possible to prevent call stack to be outputted when error occurred. So for example suppose:
set error [catch { [exec $interpName $tmpFileName] } err]
if { $error ne 0 } {
puts "err = $err" #<---- Here call stack is also outputted
}
So output now looks like:
error: some error message
while executing
[stack trace]

Tcl automatically builds up the call stack in the global variable errorInfo (and, since 8.5, in the -errorinfo member of the interpreter result options dictionary) but it is up to the calling code to decide what to do with it. The default behavior of tclsh is to print it out; other Tcl-hosting environments can do different things (it's usually recommended to print it out as it helps hunt down bugs; on the other hand, some programs — specifically Eggdrop — don't and it's a cause of much trouble when debugging scripts).
You take control of this for yourself by using catch in the script that's getting the original error. The easiest way to do this is to put the real code in a procedure (e.g., called main by analogy with C and C++) and then to use a little bit of driver code around the outside:
if {[catch {eval main $argv} msg]} {
puts "ERROR: $msg"
# What you're not doing at this point is:
# puts $errorInfo
exit 1
} else {
# Non-error case; adjust to taste
puts "OK: $msg"
exit 0
}
Note that in your code, this would go inside the script you write to $tmpFileName and not in the outer driver code that you showed (which is absolutely fine and needs no adjustment that I can think of).

Related

Why my tracing of TCL execution does not stop at compile functions?

I have the following script, ~/tmp/2.tcl:
proc p1 {a} {
if {[expr $a + 10] > 100} {
puts hi
}
}
set a [p1 200]
I have a debug-build TCL v8.6.1, I like to trace what is going on inside TCL execution when I issue "./tclsh8.6 ~/tmp/2.tcl", so I use gdb to trace the the execution (inside gdb, set args ~/tmp.2.tcl,)
What puzzled me are:
1. In `TclEvalEx`(), it is command by command parsing and execution, I do not see any
script/command compiling.
2. I set breakpoints at `TclAttemptCompileProc(), TclCompileObj()` and `TclCompileExpr`(),
they are not triggered.
What do I miss here? Why isn't there any script compiling?
Here is the backtrace of running TclEvalEx:
#0 TclEvalEx (interp=0x613680, script=0x674950 "proc p1 {a} {\n if {[expr $a + 10] > 100} {\n puts hi\n }\n}\n\nset a [p1 200]\n\n", numBytes=87, flags=0, line=1, clNextOuter=0x0,
outerScript=0x674950 "proc p1 {a} {\n if {[expr $a + 10] > 100} {\n puts hi\n }\n}\n\nset a [p1 200]\n\n") at ~/tcl8.6.1/source/generic/tclBasic.c:4935
#1 0x00007ffff7af0812 in Tcl_FSEvalFileEx (interp=0x613680, pathPtr=0x65beb0, encodingName=0x0) at ~/tcl8.6.1/source/generic/tclIOUtil.c:1809
#2 0x00007ffff7afb88f in Tcl_MainEx (argc=-1, argv=0x7fffffffde08, appInitProc=0x400963 <Tcl_AppInit>, interp=0x613680) at ~/tcl8.6.1/source/generic/tclMain.c:417
#3 0x000000000040095c in main (argc=2, argv=0x7fffffffddf8) at ~/tcl8.6.1/source/unix/tclAppInit.c:84
[UPDATE] I am not sure what was going wrong, now the breakpoints do get triggered.
The compiler has quite a few internal entry points — it's not in any way a public API, and is subject to alteration without anyone announcing it — and TclSetByteCodeFromAny and TclCompileScript appear to be among the ones that you've missed. There are others too; it's actually awkward to list them all. You probably instead ought to set a breakpoint on TclInitCompileEnv which is the standard internal function used to set up the structure used by the compiler; anything that calls it is going to be of interest to you.
FWIW, the call to proc doesn't compile the body of the procedure. That's postponed until the code is needed, i.e., until the procedure is called. The call to TclEvalEx that you were seeing won't do much meaningful compilation directly.
Also, the non-recursive execution engine used in Tcl 8.6.* makes it much harder to debug with a tool like gdb. The C stack does not reflect the Tcl stack at all.
Good luck.

Proper Error handling EXPECT / TCL

What is the best way to prevent sending commands to a dead process?
Sometimes my session gets terminated when it's supposed to be open so I end up sending commands and getting the error:
send: spawn id exp4 not open
I was trying to do something like
if [catch send "test\r"] {
puts "send error!"
}
but it seems like the query is true every pass.
that's the simplest example, but I have more complex "send / expects" where I use capture groups etc, so putting a catch around every "send / expect" or creating a function doesn't seem that useful.
can you wrap a catch around the entire program? What is the proper way to catch errors like these?
There's a FAQ written by the Expect author that addresses this: http://expect.sourceforge.net/FAQ.html#q64
Seems like you want something like
expect_before {
eof { respawning_the_process }
}
I'm sure there's some wrinkles to be ironed out (like what to do when the process is supposed to end)
The problem with this:
if [catch send "test\r"] {
is two-fold:
you did not put braces around the condition, so it's not getting evaluated at the right time.
you did not provide the right arguments to the catch command
You would want to write:
if {[catch {send "test\r"} output] != 0} {
This can be abstracted into a proc
proc send {args} {
set status [catch [list exp_send {*}$args] output]
# error handling if $status is non-zero
}
"exp_send" is a builtin alias for the expect "send" command, so it's safe to override "send" with a proc, minimizing the amount of code changes you need.

How to check if the value of file handle is not null in tcl

I have this snippet in my script:
puts "Enter Filename:"
set file_name [gets stdin]
set fh [open $file_name r]
#Read from the file ....
close $fh
Now, this snippet asks user for a file name.. which is then set as an input file and then read. But when the file with the name $file_name doesn't exists, it shows error saying
illegal file character
How do i check if fh is not null (I don't think there is a concept of NULL in tcl being "everyting is a string" language!), so that if an invalid file_name is given, i can throw a print saying file doesn't exists!
Short answer:
try {
open $file_name
} on ok f {
# do stuff with the open channel using the handle $f
} on error {} {
error {file doesn't exist!}
}
This solution attempts to open the file inside an exception handler. If open is successful, the handler runs the code you give it inside the on ok clause. If open failed, the handler deals with that error by raising a new error with the message you wanted (note that open might actually fail for other reasons as well).
The try command is Tcl 8.6+, for Tcl 8.5 or earlier see the long answer.
Long answer:
Opening a file can fail for several reasons, including missing files or insufficient privileges. Some languages lets the file opening function return a special value indicating failure. Others, including Tcl, signal failure by not letting open return at all but instead raise an exception. In the simplest case, this means that a script can be written without caring about this eventuality:
set f [open nosuchf.ile]
# do stuff with the open channel using the handle $f
# run other code
This script will simply terminate with an error message while executing the open command.
The script doesn't have to terminate because of this. The exception can be intercepted and the code using the file handle be made to execute only if the open command was successful:
if {![catch {open nosuchf.ile} f]} {
# do stuff with the open channel using the handle $f
}
# run other code
(The catch command is a less sophisticated exception handler used in Tcl 8.5 and earlier.)
This script will not terminate prematurely even if open fails, but it will not attempt to use $f either in that case. The "other code" will be run no matter what.
If one wants the "other code" to be aware of whether the open operation failed or succeeded, this construct can be used:
if {![catch {open nosuchf.ile} f]} {
# do stuff with the open channel using the handle $f
# run other code in the knowledge that open succeeded
} else {
# run other code in the knowledge that open failed
}
# run code that doesn't care whether open succeeded or failed
or the state of the variable f can be examined:
catch {open nosuchf.ile} f
if {$f in [file channels $f]} {
# do stuff with the open channel using the handle $f
# run other code in the knowledge that open succeeded
} else {
# run other code in the knowledge that open failed
}
# run code that doesn't care whether open succeeded or failed
(The in operator is in Tcl 8.5+; if you have an earlier version you will need to write the test in another manner. You shouldn't be using earlier versions anyway, since they're not supported.)
This code checks if the value of f is one of the open channels that the interpreter knows about (if it isn't, the value is probably an error message). This is not an elegant solution.
Ensuring the channel is closed
This isn't really related to the question, but a good practice.
try {
open nosuchf.ile
} on ok f {
# do stuff with the open channel using the handle $f
# run other code in the knowledge that open succeeded
} on error {} {
# run other code in the knowledge that open failed
} finally {
catch {chan close $f}
}
# run code that doesn't care whether open succeeded or failed
(The chan command was added in Tcl 8.5 to group several channel-related commands as subcommands. If you're using earlier versions of Tcl, you can just call close without the chan but you will have to roll your own replacement for try ... finally.)
The finally clause ensures that whether or not the file was opened or any error occurred during the execution of the on ok or on error clauses, the channel is guaranteed to be non-existent (destroyed or never created) when we leave the try construct (the variable f will remain with an unusable value, unless we unset it. Since we don't know for sure if it exists, we need to prevent the unset operation from raising errors by using catch {unset f} or unset -nocomplain f. I usually don't bother: if I use the name f again I just set it to a fresh value.).
Documentation: catch, chan, close, error, in operator, file, if, open, set, try, unset
Old answer:
(This answer has its heart in the right place but I'm not satified with it these months later. Since it was accepted and even marked as useful by three people I am loath to delete it, but the answer above is IMHO better.)
If you attempt to open a non-existing file and assign the channel identifier to a variable, an error is raised and the contents of the variable are unchanged. If the variable didn't exist, it won't be created by the set command. So while there is no null value, you can either 1) set the variable to a value you know isn't a channel identifier before opening the file:
set fh {} ;# no channel identifier is the empty string
set fh [open foo.bar]
if {$fh eq {}} {
puts "Nope, file wasn't opened."
}
or 2) unset the variable and test it for existence afterwards (use catch to handle the error that is raised if the variable didn't exist):
catch {unset fh}
set fh [open foo.bar]
if {![info exists fh]} {
puts "Nope, file wasn't opened."
}
If you want to test if a file exists, the easiest way is to use the file exists command:
file exists $file_name
if {![file exists $file_name]} {
puts "No such file"
}
Documentation: catch, file, if, open, puts, set, unset

exp::winnt_debug parent namespace error

I get the error "can't set "::exp::winnt_debug": parent namespace doesn't exist" when I try to run my expect script using the C implementation of expect interpreter on Windows (expect543.dll).
However the same script works fine if I run it through the ActiveState command tclsh...
The statement "set ::exp::winnt_debug 1" in the script is the cause of the error.
Any idea what might be the reason and how to resolve it?
Please find the code below
package require Expect
set ::exp::winnt_debug 1
set prompt "R4#"
set more " --More--"
expect -timeout 10 "$prompt"
set output [open result.txt "w"]
set running 1
spawn plink -telnet "144.21.12.45" -P 2004
send "enable\r"
send "\r"
send "show running-config\r"
send "\r"
while { $running > 0 } {
expect {
"\n" { puts -nonewline $output "$expect_out(buffer)" }
"$more" {send " "}
"lines *-* " { send " " }
#"$prompt" { set running 0 }
eof { set running 0 }
timeout { set running 0 }
}
}
puts "output is .."
There may be several implementations of Expect for Windows (unlike the Unix version, which has been stable for ages) and it sounds like the details of how they are implemented internally varies quite a bit between them. That's not especially surprising. Furthermore, the variable ::exp::winnt_debug is absolutely internal to a particular implementation.
The immediate fix is to change the line with the error to this:
catch {set ::exp::winnt_debug 1}
Like that, if it fails, it fails silently and won't cause the rest of the program to not run. (Enabling debugging shouldn't make any difference to whether the code runs!)
More generally, either use the ActiveState build (and work out how to package things together in the right way, bearing mind that critical dependency) or stop referring to internal features of it. It's very bad form to be poking your fingers inside the implementation of a package, as nobody ever gave a commitment to support them.

In Tcl, what is the equivalent of "set -e" in bash?

Is there a convenient way to specify in a Tcl script to immediately exit in case any error happens? Anything similar to set -e in bash?
EDIT I'm using a software that implements Tcl as its scripting language. If for example I run the package parseSomeFile fname, if the file fname does't exist, it reports it but the script execution continues. Is there a way that I stop the script there?
It's usually not needed; a command fails by throwing an error which makes the script exit with an informative message if not caught (well, depending on the host program: that's tclsh's behavior). Still, if you need to really exit immediately, you can hurry things along by putting a trace on the global variable that collects error traces:
trace add variable ::errorInfo write {puts stderr $::errorInfo;exit 1;list}
(The list at the end just traps the trace arguments so that they get ignored.)
Doing this is not recommended. Existing Tcl code, including all packages you might be using, assumes that it can catch errors and do something to handle them.
In Tcl, if you run into an error, the script will exit immediately unless you catch it. That means you don't need to specify the like of set -e.
Update
Ideally, parseSomeFile should have return an error, but looks like it does not. If you have control over it, fix it to return an error:
proc parseSomeFile {filename} {
if {![file exists $filename]} {
return -code error "ERROR: $filename does not exists"
}
# Do the parsing
return 1
}
# Demo 1: parse existing file
parseSomeFile foo
# Demo 2: parse non-existing file
parseSomeFile bar
The second option is to check for file existence before calling parseSomeFile.