tcl/tk: avoid error message about remove non existing file - tcl

In my Tcl/Tk script, there is one step to remove some txt file.
I use:
exec rm file1.txt
But if the file dose not exist, then error message will come up which will block the script usage.
What I want to do is remove the file if it exist, and if it does not exist, to skip the error.
Is there a good way of doing this?
Ok, I find the answer: file exists filename works well for this case.

You could use
file delete file1.txt
where trying to delete a non-existent file is not considered an error.

How to avoid having an error stop your program.
The "0th" solution is to use commands that don't raise errors. such as glob -nocomplain instead of just glob, or in this case file delete file1.txt as suggested by timrau.
In some cases it's impossible to prevent errors from being raised. In those cases you can choose from several strategies. Assume you need to call mycmd, and it might raise errors.
# Tcl 8.6
try mycmd on error {} {}
# Tcl 8.4 or later
catch mycmd
This invocation quietly intercepts the error and lets your program continue. This is perfectly acceptable if the error isn't important, e.g. when you attempt to discard a variable that might not exist (catch {unset myvar}).
You might want to take some action when an error is raised, such as reporting it to yourself (as an error message on stderr or in a message box, or in a log of some kind) or by dealing with the error somehow.
try mycmd on error msg {puts stderr "There was a problem: $msg"}
if {[catch mycmd msg]} {
puts stderr "There was a problem: $msg"
}
You might want to take some action only if there was no error:
try {
mycmd
} on ok res {
puts "mycmd returned $res"
} on error msg {
puts stderr "There was a problem: $msg"
}
if {[catch mycmd res]} {
puts stderr "There was a problem: $res"
} else {
puts "mycmd returned $res"
}
For instance, this invocation returns the contents of a file, or the empty string if the file doesn't exist. It makes sure that the channel is closed and the variable holding the channel identifier are destroyed in either case:
set txt [try {
open $filename
} on ok f {
chan read $f
} on error msg {
puts stderr $msg
} finally {
catch {chan close $f}
catch {unset f}
}]
Documentation: catch, chan, file, glob, if, open, puts, set, try

Related

Tcl:Is there a way to show which line in tcl throw an error?

When I source a tcl file, error will be report when run to a unknown command.
Tcl> source run.tcl
$inside tcl file$$> setup_desgin
Design setup...
Done
$inside tcl file$$> my_prove
Info: proving started
Info: ....
Info: ....
$inside tcl file$$> ::unknown my_pro
invalid command name "my_pro"
Is there a way to show the line number of the error line in tcl file as below?
Tcl> source run.tcl
$inside tcl file$$> setup_desgin
Design setup...
Done
$inside tcl file$$> my_prove
Info: proving started
Info: ....
Info: ....
$inside tcl file$$> ::unknown my_pro
invalid command name "my_pro" (run.tcl:5)
We want this because we may have a very big run.tcl with minions of line.
It's actually quite difficult to get this information in general. The accurate line number information is only available at present when the code is on the execution stack. Fortunately, you can find out what's going on with a leave-step trace (intercepting unknown would only work for unknown commands; tracing lets all errors be caught). In the code below, I've put it on eval, but in a more practical example you'd put it on source or something like that.
set errorlineSet 0
proc LEAVESTEP {cmd code result op args} {
global errorline errorlineSet
if {$code == 1} {
if {!$errorlineSet} {
set errorline [dict get [info frame -4] line]
}
set errorlineSet 1
} else {
set errorlineSet 0
}
}
try {
trace add execution eval leavestep LEAVESTEP
eval {
sdfgsldfjg; # This is line 17
}
} on error {msg opt} {
puts "ERROR: $msg (line: $errorline) (local line: [dict get $opt -errorline])"
} finally {
trace remove execution eval leavestep LEAVESTEP
}
When I save all that to a file and run it, it prints this:
ERROR: invalid command name "sdfgsldfjg" (line: 17) (local line: 3)
As you can see, there is also the -errorline key in the result option dictionary, but that's mapped to 3 in this case and that's rather misleading! It uses that value because of backward compatibility, but I'm not convinced that it is all that helpful.
You can redefine the unknown command and use the info frame command in it to get the location. Something like:
# Save the default unknown if you want
rename ::unknown ::original_unknown
proc ::unknown {name args} {
set caller [info frame -1]
dict with caller {
switch $type {
source {
puts stderr "invalid command name \"$name\" (in file $file:$line)"
}
proc {
if {[info exists lambda]} {
puts stderr "invalid command name \"$name\" (in lambda $lambda:$line)"
} else {
puts stderr "invalid command name \"$name\" (in proc $proc:$line)"
}
}
eval {
puts stderr "invalid command name \"$name\" (in eval {$cmd}:$line)"
}
precompiled {
puts stderr "Invalid command name \"$name\""
}
}
}
}
Example usage:
% source my_unknown.tcl
% source bad.tcl
invalid command name "put" (in file /home/shawn/src/bad.tcl:3)

How to control the character length of strings in tcl errors?

Long strings which appear in tcl error messages are elided with ... after 150 characters:
proc helloWorld {a} {
throw
}
helloWorld "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
Error:
invalid command name "throw"
while executing "throw "
(procedure "helloWorld" line 2)
invoked from within "helloWorld "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff..."
(file "mytcl" line 6)
Is there a way to change this so that more characters are shown?
In my large commercial tcl application, customers use file paths that are more than 150 characters long. We see 100 characters for a repository name, plus 100 characters for a location within the repository. Since only 150 characters appear, this means the "useful" part of the filename is not displayed in error messages. We have suggested to the customer that they could shorten the path using a symlink, but they do not accept this.
You can install your own background error handler.
You can log the error, display it how you want, etc.
interp bgerror {} ::bgerrhandler
# give err and res whatever names that make sense to you
proc ::bgerrhandler { err res } {
# do stuff
return
}
The problem that you've got is that the line-shortening is applied during the construction of the error trace message. It's already long gone by the time you get to see it. What you need instead is to install a custom background error handler using the new API that picks critical things out of the error stack. (The old API using a simple bgerror command is unsuitable for this task as it hides the result option dictionary for backward compatibility reasons.)
proc MyErrorHandler {msg opts} {
if {[dict get $opts -code] == 1} {
# Actually an error!
puts "ERROR: $msg"
foreach {op details} [dict get $opts -errorstack] {
puts stderr "$op :> $details"
}
return
}
# Transfer to standard implementation; tailcall recommended for this from 8.6 on
::tcl::Bgerror $msg $opts
}
interp bgerror {} MyErrorHandler
Here's an example of this working:
# Error generation code
proc foo {x} {throw BOOM $x}
proc bar {y} {foo $y$y$y$y}
proc grill {} {bar skdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsd}
# Now actually do things in the background
after idle grill; after 50 set z 1; vwait z
That will print this message:
ERROR: skdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsd
INNER :> returnImm skdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsd {-errorcode BOOM}
CALL :> foo skdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsd
CALL :> bar skdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsd
CALL :> grill
The INNER indicates what bytecode opcode actually threw, and then there's a sequence of CALLs to give the exact arguments lists that lead to that point.

why status of the child process is non-zero?

Consider this code:
set status [catch {eval exec $Executable $options | grep "(ERROR_|WARNING)*" ># stdout} errorMessage]
if { $status != 0 } {
return -code error ""
}
In case of errors in the child process, they are outputted in the stdout. But if there are no errors in the child process, the status value still non-zero. How avoid this?
Also is there are some ways to use fileutil::grep instead of bash grep?
In case of errors in the child process, they are outputted in the stdout. But if there are no errors in the child process, the status value still non zero. How avoid this?
There's no connection between writing something to any file descriptor (including the one connected to the "standadrd error stream") and returning a non-zero exit code as these concepts are completely separate as far as an OS is concerned. A process is free to perform no I/O at all and return a non-zero exit code (a somewhat common case for Unix daemons, which log everything, including errors, through syslog), or to write something to its standard error stream and return zero when exiting — a common case for software which write certain valuable data to their stdout and provide diagnostic messages, when requested, to their stderr.
So, first verify your process writes nothing to its standard error and still exits with non-zero exit code using plain shell
$ that_process --its --command-line-options and arguments if any >/dev/null
$ echo $?
(the process should print nothing, and echo $? should print a non-zero number).
If the case is true, and you're sure the process does not think something is wrong, you'll have to work around it using catch and processing the extended error information it returns — ignoring the case of the process exiting with the known exit code and propagating every other error.
Basically:
set rc [catch {exec ...} out]
if {$rc != 0} {
global errorCode errorInfo
if {[lindex $errorCode 0] ne "CHILDSTATUS"} {
# The error has nothing to do with non-zero process exit code
# (for instance, the program wasn't found or the current user
# did not have the necessary permissions etc), so pass it up:
return -code $rc -errorcode $errorCode -errorinfo $errorInfo $out
}
set exitcode [lindex $errorCode 2]
if {$exitcode != $known_exit_code} {
# Unknown exit code, propagate the error:
return -code $rc -errorcode $errorCode -errorinfo $errorInfo $out
}
# OK, do nothing as it has been a known exit code...
}
CHILDSTATUS (and the errorCode global variable in general) is described here.

What's the difference between return -code error and error

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"

How to change Tcl error prefix?

The Tcl error command writes the specified message to stderr adding to it a prefix "Error: ".
Is it possible to change that prefix with something else like "ERROR: " or "MyError: " ?
The error command itself does not write anything to anywhere other than the Tcl result. What it actually does is throw an exception (of type “error”; TCL_ERROR at the C API level). The code that prefixes it with “Error: ” is just the standard fallback handler, but you can use your own by having your main script use catch to trap any result and decide what to do with it (or try … from 8.6 onwards, which is easier to use when trapping specific problems).
if {[catch {source realscript.tcl} msg]} {
# Caught an error!
puts stderr "Oh noes! Teh errorz iz atakkin! $msg"
puts stderr $::errorInfo
exit 1
}
OK, if you're using 8.5 then a slightly better way to write that is:
if {[catch {source realscript.tcl} msg resultdict]} {
# Caught an error!
puts stderr "Oh noes! Teh errorz iz atakkin! $msg"
puts stderr [dict get $resultdict -errorinfo]
exit 1
}
There's lots of other interesting stuff in the result dictionary, which you might or might not be interested in. Have a poke around.