How to run external script from Tk and make it throw output to the console?

Estet: There is a some small Tk application (let's call it "launcher") which has been made for starting another script (let's call it "rocket" :-) ). I mean I run this launcher, choose/set some parameters and press button "run". Then it creates some config file which is a kind of an input for rocket. Then it should run rocket... And here I've got a problem. I need to see the process of rocket running - if I start it manually from console I see a lot of logs. I tried to run it using exec:

if [catch {exec rocket} errMsg] {
    puts "Exception: $errMsg"
    exit
}

But it seems rocket starts logging into it's own output or whatever - I can't see anything in the console. And I haven't found any way to redirect it somewhere else except files.

Who knows, how to drive the output flow of child process? Or maybe there is another way to resolve the initial task?


AMG: You will need to capture its stdout and stderr and write to a text widget. Here's the simplest way:

set command {ls /}            ;# Unix example
set command {cmd /c dir C:\\} ;# Windows example

package require Tk
text .t -yscrollcommand {.s set} -state disabled
scrollbar .s -orient vertical -command {.t yview}
pack .s -side right -fill y
pack .t -side left -fill both -expand 1
proc receive {chan} {
    .t configure -state normal
    .t insert end [read $chan]
    .t configure -state disabled
    .t see end
    if {[eof $chan]} {
        close $chan
    }
}
set chan [open |[concat $command 2>@1]]
fconfigure $chan -blocking 0
fileevent $chan readable [list receive $chan]

Things get tricky if you want to handle stdout and stderr separately, for instance give them different colors. See [1 ] and a way to 'pipe' from an external process into a text widget for more information.


Estet: Cool! It works, but it's not quite clear for me how :) (I'm not pro at tcl) I understand Tk area but:

1) What does this do?

open |[concat $command 2>@1]

AMG: [concat] concatenates lists. The first list is $command, and the second list is 2>@1. (Most of the time, a single element value is usable directly as a single-element list. But be very careful if the element contains spaces or braces or quotes!) The result is that [concat] returns the $command list with 2>@1 appended. The argument to [open] is that list, preceded by a | character. [open] interprets the | to mean that it's starting a command pipeline instead of opening a file, and it interprets the final 2>@1 to mean that it should merge stderr into stdout. [open] then returns a channel name that is used to read the combined stdout/stderr from the command pipeline. There are lots of other options, in case you want to run multiple commands in the pipeline, or you want to write to it instead of or in addition to reading from it. See [exec] for more information on the special syntax accepted for command pipelines.

Estet: 2) What is the difference between "-blocking 0" and "-blocking 1" ?

AMG: "-blocking 0" puts the channel in non-blocking mode, which means that [read] and other such I/O commands will not block program execution indefinitely. Instead they will read or write as much as they can and then return, even if they didn't totally complete the job. (For commands that write, I'm pretty sure the unwritten data will still go into an output buffer rather than be lost.) Nonblocking mode only works properly in combination with I/O notification, explained next.

Estet: 3) Do I understand correctly that

fileevent $chan readable [list receive $chan]

means that text widget is updating every time when something appears from $chan ?

AMG: That's the goal. What it literally means is that the [receive] command will be called whenever reading from $chan will not block. That happens when there's data to be read, or $chan has reached end-of-file. [receive] is called with one argument: $chan. Notice that [receive] is responsible for detecting [eof] and [close] $chan. If it fails to do this, it will get called repeatedly upon end-of-file, and the program will lock up.

Estet: By the way, one more solution has been suggested (also works):

[exec /usr/openwin/bin/xterm -e /bin/bash -c "script;read&"]

This way we are able to close main window with keeping script running in separate. How do you think, with what problems I can come across using it?

AMG: That's fine, however it gives you almost no control over the display. Also it's not portable to other operating systems, only Unix-style ones with /usr/openwin/bin/xterm and /bin/bash. The example I gave should work anywhere. Well, maybe it won't work right on Windows 95, but then again, nothing does. ;^)

My approach allows you to embed the text widget into your application's window hierarchy, for example on a diagnostics tab on a ttk::notebook. With a bit more work, shown at [2 ], you can display stdout and stderr in different colors, and you can programmatically write to the command's stdin. You can timestamp the text that comes from the command pipeline. You can even provide checkboxes to show and hide the timestamps or the colors. You can multiplex the output from multiple commands into a single log, with tags and/or colors identifying where it all came from. You can log the text to disk and/or send it over the network. You can display pop-up alerts or ring the [bell] whenever there's input, or only when it's on stderr. Whatever. Anything you can imagine. You get none of those options with xterm, but if you don't need them, by all means use xterm. ;^) At work I recently used your xterm trick to spawn a child Kermit process, but I had to do it using Ada. (Boo!)


Estet: Powerful explanation! I definitely will implement it in future projects. But for this time xterm is enough. One more reason - the script I run from Tk can have quite long duration and it would be good to have possibility to close the main Tk window.

AMG: You can have multiple Tk windows, and nothing's forcing you to use "." as the "main" window. Also you can use [wm protocol] to set the WM_DELETE_WINDOW handler to run a command that does [wm withdraw] on the window, without terminating your program. (But see George Petasis's note on the [wm withdraw] page.)


Estet: One more thing I'd like to understand is 2>@1. From description it's clear that I redirect stdout and stderr to some channel Id. But what is @1? What is the channel?

RLE (2011-04-22) The "2>@1" operator is Tcl's version of the output file descriptor duplicate operator from Unix shells that support that syntax. Below is the Bash manpage description of the operator:

 The operator

   [n]>&word

 is used similarly to duplicate output file descriptors.  If n is not
 specified, the standard output (file descriptor 1) is used.  If the
 digits  in word do not specify a file descriptor open for output, a re-
 direction error occurs.  As a special case, if n is omitted, and word
 does not expand to one or more digits, the standard output and standard
 error are redirected as described previously.

Which probably does not help much. The Tcl exec command manpage description is better:

 2>@1           Standard error from all commands in the pipeline is
                redirected to the command result.  This operator is only
                valid at the end of the command pipeline.

You can think of it as taking stderr "the '2' refers to stderr" and redirecting it into stdout "the '1' refers to stdout". That is not actually what happens, but it nicely describes the result of this one particular usage.


Estet: BTW, it's beyond the tcl/tk theme, but maybe you know. In case if I use that idea with xterm the console closes by pressing 'enter' key after script is done. Are there any ways to avoid of this? I mean to make console not to close if I press 'enter'

AMG: That behavior is caused by the "read" shell command in your "exec xterm" command. Without "read", the xterm would close immediately. You want it to remain open indefinitely, or at least until the xterm window is forcibly closed. I haven't been able to locate a standard Unix command that sleeps forever, but you might try replacing "read" with "sleep 2147483647". Sixty-eight years is probably close enough to forever. ;^)


gold 25Nov2017, added pix from windows example and some categories.

Ref links, Combining Fortran and Tcl in one program, Managing Fortran programs, Generating wrappers for C and Fortran,A do-it-yourself IDE with the text widget

How to run external script from Tk and more png