[Frank Pilhofer]: I am currently writing a Tk program that frequently needs to exec external programs. I became annoyed that the GUI becomes unresponsive during [exec], especially that there are no updates. For example, if any parts of the window become obscured and un-obscured again (e.g. if you drag another window across it), the GUI is not redrawn. This is ugly, and people get the impression that the application has crashed. Therefore, I wrote the following tk_exec drop-in replacement for exec. It is supposed to work the same, but it keeps the event loop rolling as it waits for the external program to complete. After completion, it returns the program's stdandard output. I have not yet tested it extensively. Feel free to edit, update and fix. ---- proc tk_exec_fileevent {id} { global tk_exec_data global tk_exec_cond global tk_exec_pipe if {[eof $tk_exec_pipe($id)]} { fileevent $tk_exec_pipe($id) readable "" set tk_exec_cond($id) 1 return } append tk_exec_data($id) [read $tk_exec_pipe($id) 1024] } proc tk_exec {args} { global tk_exec_id global tk_exec_data global tk_exec_cond global tk_exec_pipe global tcl_platform global env if {![info exists tk_exec_id]} { set tk_exec_id 0 } else { incr tk_exec_id } set keepnewline 0 for {set i 0} {$i < [llength $args]} {incr i} { set arg [lindex $args $i] switch -glob -- $arg { -keepnewline { set keepnewline 1 } -- { incr i break } -* { error "unknown option: $arg" } ?* { # the glob should be on *, but the wiki reformats # that as a bullet break } } } if {$i > 0} { set args [lrange $args $i end] } if {$tcl_platform(platform) == "windows" && \ [info exists env(COMSPEC)]} { set args [linsert $args 0 $env(COMSPEC) "/c"] } set pipe [open "|$args" r] set tk_exec_pipe($tk_exec_id) $pipe set tk_exec_data($tk_exec_id) "" set tk_exec_cond($tk_exec_id) 0 fconfigure $pipe -blocking 0 fileevent $pipe readable "tk_exec_fileevent $tk_exec_id" vwait tk_exec_cond($tk_exec_id) if {$keepnewline} { set data $tk_exec_data($tk_exec_id) } else { set data [string trimright $tk_exec_data($tk_exec_id) \n] } unset tk_exec_pipe($tk_exec_id) unset tk_exec_data($tk_exec_id) unset tk_exec_cond($tk_exec_id) if {[catch {close $pipe} err]} { error "pipe error: $err" } return $data } ---- Some issues: * A "background" exec (with &) is not handled yet. * The case that stdout is redirected is not handled yet. [[Nice work, Frank.]] The proposed tk_exec routine suffers from thread safety problems, in particular if it is used to launch two processes and they exit in reverse order (the 2nd terminates first) you get an error. The problem is that after the vwait function some variables are unset using a global index (tk_exec_id) which has been in the mean time incremented by the 2nd process start. The simple trick is to copy the global tk_exec_id in a local "id" variable and use this one. The patch file follows ---- # --- patch begin --- --- tk_exec.tcl 2006-11-03 19:12:04.000000000 +0100 +++ tk_exec_old.tcl 2006-11-03 19:18:34.000000000 +0100 @@ -64,20 +64,21 @@ set tk_exec_data($tk_exec_id) "" set tk_exec_cond($tk_exec_id) 0 + set id $tk_exec_id fconfigure $pipe -blocking 0 - fileevent $pipe readable "tk_exec_fileevent $tk_exec_id" + fileevent $pipe readable "tk_exec_fileevent $id" - vwait tk_exec_cond($tk_exec_id) + vwait tk_exec_cond($id) if {$keepnewline} { - set data $tk_exec_data($tk_exec_id) + set data $tk_exec_data($id) } else { - set data [string trimright $tk_exec_data($tk_exec_id) \n] + set data [string trimright $tk_exec_data($id) \n] } - unset tk_exec_pipe($tk_exec_id) - unset tk_exec_data($tk_exec_id) - unset tk_exec_cond($tk_exec_id) + unset tk_exec_pipe($id) + unset tk_exec_data($id) + unset tk_exec_cond($id) if {[catch {close $pipe} err]} { error "pipe error: $err" # --- patch end --- Thank you for this work ! Stefan (stefan@schippers.it) ---- Dave Snyderman points out that Brent's book [http://mini.net/tcl/852.html] includes an ExecLog that does much of the same. ---- The [BLT] extension has a command bgexec which is similar. Example code from the BLT docs (slightly adapted): proc widgetwrite { data } { # Put your output-handling code here, e.g. .t insert end $data } global myStatus blt::bgexec myStatus -onoutput widgetwrite du -s $dir Tk will continue to handle events while the external command is running; whenever the command writes to its stdout, the output is delivered by a call to the proc named by "-onoutput": no need to wait for the command to complete. ---- [FPX]: I just made a change to add "command /c" before the command to be executed if the platform is windows. I got weird results (hangs) in Windows otherwise. - [RS]: Note however that ''command.com'' is the weaker DOS shell - on Win NT/2K you have ''cmd.exe'' (but command.com is still there); the default shell can be found in ::env(COMSPEC). - [FPX]: okay, so I've replaced "command" with env(COMSPEC) above. '''[DGP]''' You ought to take one more step and make use of [auto_execok].