[sbron]: In unix/linux there is a standard command named https://man7.org/linux/man-pages/man1/tee.1.html%|%''tee''%|%, that duplicates its input to both standard output and a file. Inspired on that concept, I came up with some code to add similar functionality to Tcl. The module adds a ''tee'' command to Tcl. It has three subcommands: '''tee replace''' ''chan'' ''file'': Copy all data sent to ''chan'' to ''file''. ''chan'' must have been opened for writing. If ''file'' exists, it is truncated. This is the default, so it can also be invoked as: '''tee''' ''chan'' ''file''. '''tee append''' ''chan'' ''file'': Copy all data sent to ''chan'' to ''file''. ''chan'' must have been opened for writing. If ''file'' exists, the data is appended. '''tee channel''' ''chan'' ''fd'': Copy all data sent to ''chan'' to an already open ''fd''. Both ''chan'' and ''fd'' must have been opened for writing. The commands return a handle to the secondary output file to allow the caller to configure things like encoding, buffering, and baud rate. It is even possible to stack another transchan on top, like [timestamp]. To stop copying the data to file and return ''chan'' to normal, use: '''chan pop''' ''chan''. This closes the secondary output file. (Also in the case of '''tee channel''' ''chan'' ''fd''!) '''Usage example:''' ====== package require tee package require timestamp set tee [tee stdout /tmp/sessionlog.txt] try { # Make the output line buffered, so time stamps will be accurate fconfigure $tee -buffering line # Add time stamps only to the copy going to the log file timestamp channel $tee puts "Hello, World!" } finally { # Return stdout to normal, even in case of errors chan pop stdout } ====== '''Implementation''' Put the following code in a file called ''tee-1.0.tm'' and place it somewhere in the tcl::tm path: ====== namespace eval tee { variable methods {initialize finalize write} namespace ensemble create -subcommands {replace append channel} \ -unknown [namespace current]::default namespace ensemble create -command transchan -parameters fd \ -subcommands $methods } proc tee::default {command subcommand args} { return [list $command replace $subcommand] } proc tee::channel {chan fd} { chan push $chan [list [namespace which transchan] $fd] return $fd } proc tee::replace {chan file} { return [channel $chan [open $file w]] } proc tee::append {chan file} { return [channel $chan [open $file a]] } proc tee::initialize {fd handle mode} { variable methods return $methods } proc tee::finalize {fd handle} { close $fd } proc tee::write {fd handle buffer} { puts -nonewline $fd $buffer return $buffer } ====== ---- '''[wusspuss] - 2023-05-10 09:27:25''' There seems to be a https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/#bug-1-backpressure%|%backpressure%|% problem with that approach however. Specifically: If $fd (the channel we're copying to) is slower than $chan (what we're copying from), what may happen is * if it's blocking, the entire program will hang while it is being written to * if it's non-blocking, more and more RAM will be used to buffer writes until we run out of RAM Both of these are quite important in a server (two ways to potentially DoS basically), but in many cases will never popup at all. I don't think this can be fixed so long as we're confining ourselves to a transchan. I think a better approach would be: read a chunk from input, wait for output1 to be writable, write, wait for output2 to be writable, write. This would be quite tricky to do with callbacks - the simplest way would probably be to create a coroutine::util wrapper for puts (one for read already exists), and basically just ====== # theoretical, doesn't work proc tee {input output1 output2} { set chunk [coroutine::util read $input 1024] coroutine::util puts $output1 $chunk coroutine::util puts $output2 $chunk } ====== This would still be rather awkward to actually use because AFAIK there's no bidirectional [chan pipe] version. And no [chan push]/pop interface of course. So a number of things would need to be implemented. I think that can be seen as room for improvement for both [chan] and [coroutine] <>Channel