I am programming TCL for Cisco TCL/IVR inside voice gateway.
I have some text files with public holidays dates, which I need to open on the primary server. If an error is returned while opening the file, I want to try to use a secondary backup server.
code:
if [catch {set fd [open $filename] } errmsg] {
error "Unable to open the file: $filename on Primary Server \n $errmsg"
set filename $httpsvr2$textfile
if [catch {set fd [open $filename] } errmsg] {
error "Unable to open the file: $filename on Primary Server \n $errmsg"
set Read 0
}
else {
set Read 1
}
}
else {
set Read 1
}
I was trying to use the Read flag; if it is 1, then I will search inside the file. If it is 0, it's because the file couldn't be opened on any of the servers, so I will just treat the call as if it's a working (non-holiday) day.
However, in my current code when the first attempt to open the file fails, it automatically stops executing the script.
How could I continue executing after the first error? Should I make a procedure and return values like -1? If so, how could I do that?
The command error exits the script (meaning once error is reached, you could say that execution stops). You would probably be better off by putsing the error message through stderr or a more suitable channel:
puts stderr "Unable to open the file: $filename on Primary Server \n $errmsg"
I would make it a procedure like you are thinking
proc openFils { filename httpsvr2 textfile ) {
set fd -1
foreach f $filename $httpsvr2$textfile {
if { ! [file exists $f] } {
puts stderr "File $f not on system"
}
if [catch {set fd [open $f] } errmsg] {
puts stderr "Unable to open the file: $f \n $errmsg"
} else {
break
}
}
return $fd
}
Now you can perform an operation on the file handle 'fd', note: the above script will open and return the first file it can, not both.
Related
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)
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 running tclsh some.tcl and it exits after it hits eof. I want it not to exit and gives control to user for interaction. Note that we can do this by invoking shell and sourcing script but that doesn't solve my problem as it cannot be used in automation.
If you can load the TclX package (old but still useful) then you can do:
package require Tclx; # Lower case at the end for historical reasons
# Your stuff here
commandloop
That's very much like how Tcl's own interactive command line works.
Otherwise, here's a scripted version that does most of what an interactive command session does:
if {![info exists tcl_prompt1]} {
set tcl_prompt1 {puts -nonewline "% ";flush stdout}
}
if {![info exists tcl_prompt2]} {
# Note that tclsh actually defaults to not printing anything for this prompt
set tcl_prompt2 {puts -nonewline "> ";flush stdout}
}
set script ""
set prompt $tcl_prompt1
while {![eof stdin]} {
eval $prompt; # Print the prompt by running its script
if {[gets stdin line] >= 0} {
append script $line "\n"; # The newline is important
if {[info complete $script]} { # Magic! Parse for syntactic completeness
if {[catch $script msg]} { # Evaluates the script and catches the result
puts stderr $msg
} elseif {$msg ne ""} { # Don't print empty results
puts stdout $msg
}
# Accumulate the next command
set script ""
set prompt $tcl_prompt1
} else {
# We have a continuation line
set prompt $tcl_prompt2
}
}
}
Getting the remaining bits right (e.g., the interaction with the event loop when the Tk package is loaded) would require quite a bit more complexity...
I have a variable, let's say xx, with a list of index 0 and index 1 values. I want to modify a script (not mine) which previously defines a function, pptable, i.e.,
proc pptable {l1 l2} {
foreach i1 $l1 i2 $l2 {
puts " [format %6.2f $i1]\t[format %6.2f $i2]"
}
}
so that it displays the output into two columns using
pptable [lindex $xx 1] [lindex $xx 0]
However, I want to write the output directly to a file. Could you tell me how I can send the data to a file instead to the display?
One of the neatest ways of doing this is to stack on a channel transform that redirects stdout to where you want it to go. This works even if the write to stdout happens from C code or in a different thread as it plugs into the channel machinery. The code is a little bit long (and requires Tcl 8.6) but is reliable and actually mostly very simple.
package require Tcl 8.6; # *REQUIRED* for [chan push] and [chan pop]
proc RedirectorCallback {targetHandle op args} {
# The switch/lassign pattern is simplest way of doing this in one procedure
switch $op {
initialize {
lassign $args handle mode
# Sanity check
if {$mode ne "write"} {
close $targetHandle
error "this is just a write transform"
}
# List of supported subcommands
return {initialize finalize write}
}
finalize {
lassign $args handle
# All we need to do here is close the target file handle
close $targetHandle
}
write {
lassign $args handle buffer
# Write the data to *real* destination; this does the redirect
puts -nonewline $targetHandle $buffer
# Stop the data going to *true* stdout by returning empty string
return ""
# If we returned the data instead, this would do a 'tee'
}
default {
error "unsupported subcommand"
}
}
}
# Here's a wrapper to make the transform easy to use
proc redirectStdout {file script} {
# Stack the transform onto stdout with the file handle to write to
# (which is going to be $targetHandle in [redirector])
chan push stdout [list RedirectorCallback [open $file "wb"]]
# Run the script and *definitely* pop the transform after it finishes
try {
uplevel 1 $script
} finally {
chan pop stdout
}
}
How would we actually use this? It's really very easy in practice:
# Exactly the code you started with
proc pptable {l1 l2} {
foreach i1 $l1 i2 $l2 {
puts " [format %6.2f $i1]\t[format %6.2f $i2]"
}
}
# Demonstrate that stdout is working as normal
puts "before"
# Our wrapped call that we're capturing the output from; pick your own filename!
redirectStdout "foo.txt" {
pptable {1.2 1.3 1.4} {6.9 6.8 6.7}
}
# Demonstrate that stdout is working as normal again
puts "after"
When I run that code, I get this:
bash$ tclsh8.6 stdout-redirect-example.tcl
before
after
bash$ cat foo.txt
1.20 6.90
1.30 6.80
1.40 6.70
I believe that's precisely what you are looking for.
You can do this with less code if you use Tcllib and TclOO to help deal with the machinery:
package require Tcl 8.6
package require tcl::transform::core
oo::class create WriteRedirector {
superclass tcl::transform::core
variable targetHandle
constructor {targetFile} {
set targetHandle [open $targetFile "wb"]
}
destructor {
close $targetHandle
}
method write {handle buffer} {
puts -nonewline $targetHandle $buffer
return ""
}
# This is the wrapper, as a class method
self method redirectDuring {channel targetFile script} {
chan push $channel [my new $targetFile]
try {
uplevel 1 $script
} finally {
chan pop $channel
}
}
}
Usage example:
proc pptable {l1 l2} {
foreach i1 $l1 i2 $l2 {
puts " [format %6.2f $i1]\t[format %6.2f $i2]"
}
}
puts "before"
WriteRedirector redirectDuring stdout "foo.txt" {
pptable {1.2 1.3 1.4 1.5} {6.9 6.8 6.7 6.6}
}
puts "after"
I assume you don't want or can't modify the existing script and proc pptable, correct?
If so, there are different options, depending on your exact situation:
Redirect stdout: tclsh yourscript.tcl > your.out
Redefine puts (for a clearly defined scope):
rename ::puts ::puts.orig
proc puts args {
set fh [open your.out w];
::puts.orig $fh $args;
close $fh
}
# run pptable, source the script
This theme has been covered before, e.g., tcl stop all output going to stdout channel?
Rewire Tcl's stdout channel (not necessarily recommended):
close stdout
open your.out w
# run pptable, source the script
This has also been elaborated on before, e.g. Tracing stdout and stderr in 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