how can I redirect a proc output into a file in tcl, for example, I have a proc foo, and would like to redirect the foo output into a file bar. But got this result
To accomplish this I wrapped the call to my Tcl proc in a shell script. This works on unix, not sure about other OS.
file - foo.tcl:
proc foo {} {puts "Hi from foo"}
file - foo.csh (any shell script will work, I'm using csh for this example):
enter code here
#!/usr/bin/tclsh source foo.tcl eval "foo $argv"
file - main.tcl:
exec foo.csh > ./myoutput.txt
Of course these commands can be made 'fancy' by wrapping them in safety measures like catch, etc... for clarity sake I didn't include them, but I would recommend their use. Also I included $argv which isn't needed since proc foo doesn't take args, but typically IRL there will be args. Be sure to use >> if you just want to append to the file rather than overwriting it.
At present when you type foo > bar
Tcl is trying to run a foo proc that takes 2 parameter, as this doesn't exist you get the error message. I can think of two ways you could tackle this problem.
You can redirect at the top level, so when you run tclsh tclsh > bar
then all of the output will be redirected, however I doubt this is what you want.
You could change foo so that it accepts an open file as a parameter and write to that:
proc foo {fp} {
puts $fp "some text"
}
set fp [open bar w]
foo $fp
close $fp
Generally, I'd recommend the stdout redirection that Donal Fellows suggests in his answer.
Sometimes this may not possible. Maybe the Tcl interpreter is linked into a complex application that has itself a fancy idea of where the output should go to, and then you don't know how to restore the stdout channel.
In those cases you can try to redefine the puts
command. Here's a code example on how you could do that. In plain Tcl, a command can be redefined by renaming it into a safe name, and creating a wrapper proc that calls the original command at the safe name - or not at all, depending on your intended functionality.
proc redirect_file {filename cmd} {
rename puts ::tcl::orig::puts
set mode w
set destination [open $filename $mode]
proc puts args "
uplevel \"::tcl::orig::puts $destination \$args\"; return
"
uplevel $cmd
close $destination
rename puts {}
rename ::tcl::orig::puts puts
}
You can also redirect text into a variable:
proc redirect_variable {varname cmd} {
rename puts ::tcl::orig::puts
global __puts_redirect
set __puts_redirect {}
proc puts args {
global __puts_redirect
set __puts_redirect [concat $__puts_redirect [lindex $args end]]
set args [lreplace $args end end]
if {[lsearch -regexp $args {^-nonewline}]<0} {
set __puts_redirect "$__puts_redirect\n"
}
return
}
uplevel $cmd
upvar $varname destination
set destination $__puts_redirect
unset __puts_redirect
rename puts {}
rename ::tcl::orig::puts puts
}
The Tcl'ers Wiki has another interesting example of redefining puts in more complex applications. Maybe this is inspiring as well.
This should work:
% proc foo {} { return "hello world" }
% foo
hello world
% exec echo [foo] > bar
% exec cat bar
hello world
% exec echo [foo] >> bar
% exec cat bar
hello world
hello world
%
Thanks for sharing CFI. I would like to contribute as well. so, i made some changes to redefine puts on dump to file on startlog and end logging on endlog to file when ever we want. This will print on stdout and also redirect stdout to file.
proc startlog {filename } {
rename puts ::tcl::orig::puts
set mode w
global def destination logfilename
set destination [open $filename $mode]
set logfilename $filename
proc puts args "
uplevel 2 \"::tcl::orig::puts \$args\"
uplevel \"::tcl::orig::puts $destination \{\$args\}\"; return
"
}
proc endlog { } {
global def destination logfilename
close $destination
rename puts {}
rename ::tcl::orig::puts puts
cleanlog $logfilename
puts "name of log file is $logfilename"
}
proc cleanlog {filename } {
set f [open $filename]
set output [open $filename\.log w]
set line1 [regsub -all {\-nonewline stdout \{} [read $f] ""]
set line2 [regsub -all {stdout \{} $line1 ""]
set line3 [regsub -all {\}} $line2 ""]
set line4 [regsub -all {\{} $line3 ""]
puts $output $line4
close $output
file rename -force $filename.log $filename
}
We can try in this way also
% proc foo {} { return "hello world" }
% foo
hello world
% set fd [open "a.txt" w]
file5
% set val [foo]
hello world
% puts $fd $val
% close $fd
% set data [exec cat a.txt]
hello world