Read both a file and stdin

When you start tclsh or wish (on Unix and perhaps a few other platforms) without a filename, you will get a prompt so you can interact with Tcl. (For wish on Windows, use console show instead). If you start them with a filename, that script is executed, but the interactive prompt does not appear.

What if you want both? Donald Porter presented this solution on comp.lang.tcl:

 set cmd ""
 while 1 {
    if { "$cmd" != "" } {
            # Why "format catch"?  It "is a legacy workaround
            #     for an old bytcode compiler bug."  We should
            #     find out when it's fixed, and "package require
            #     Tcl ..." appropriately.
        catch {[format catch] $::tcl_prompt2}
    } else {
        if {[catch {[format catch] $::tcl_prompt1}]} {
          puts -nonewline "% "
    flush stdout
    append cmd \n[gets stdin]
    if [ info complete $cmd ] {
        catch $cmd res
        puts $res
        set cmd ""

Other improvements that could still be made:

  • avoid using global variables cmd and res
  • properly handle scripts that return codes other than 0 (TCL_OK) or 1 (TCL_ERROR) (for example, [break])

Note that the code above is best understood as an improvement on the implementation developed in this Usenet thread:

Given the original problem description, the right solution in wish is [console show]. With a little hacking you can have a console for Unix, or better yet, make use of TkCon [L1 ].

For tclsh only, no console widget is available, so then I would go ahead and use [vwait forever] to get an event loop going and use [fileevent stdin readable] to collect and evaluate interactive commands from stdin rather than the [while] loop above.


There is similar code at Integrating Tcl and Emacs on Windows for a more specific purpose.

Simple example: from stdio to variable

Instead, this is simple example may be useful for newbies (like myself, GV). This reads multiple lines from the standard input (stdin) and keeps all the lines in a variable. Last input line must be a dot (".").

  set n 0
  while {$n<3000} {
    gets stdin row
    puts $row
    if { [string equal $row "."] } { break };
    append message $row "\n"
  puts "---------"
  puts $message