From AlphaWiki

Pipes: Error pipes and window pipes

Error pipes

One problem with the pipe usage described in Pipes.PipesAndShellLikeInteraction, [BatchLikeInteraction], etc. is that stderr from the external process is not captured by those simple pipes. This may be irrelevant for some programmes like MAXIMA who doesn't write to 'stderr' anyway, but for other programmes it is central obstacle because for example the prompt may be transmitted via this channel. (This is the case with 'coqtop' for example.)

The problem is that open | doesn't support 'stderr'. It does support redirection of 'stderr', but then we need another pipe to redirect to... This auxiliary pipe should just listen and repeat. How to set up such a pipe was explained by Vince? on Alpha-D: just open a pipe to 'cat'! (This is used in appPaths.tcl.)

 
    # Set up a listen-and-repeat pipe for stderr:
    set errorPipe [open "|/bin/cat" RDWR]
    fconfigure $errorPipe -buffering line -blocking 0

    # Open the main pipe, redirecting stderr:
    set mainPipe [open "|foo 2>@ $errorPipe" RDWR]
    fconfigure $mainPipe -buffering line -blocking 0

    # And then of course set up handlers for both pipes...

Exactly the same effect can be obtained using the pipe command from Extended Tcl (which is loaded automatically in a standard AlphaX installation, no?):

 
    # Set up a listen-and-repeat pipe for stderr:
    pipe errorPipe errorIn
    fconfigure $errorPipe -buffering line -blocking 0
    fconfigure $errorIn -buffering none -blocking 0

    # Open the main pipe, redirecting stderr:
    set mainPipe [open "|foo 2>@ $errorIn" RDWR]
    fconfigure $mainPipe -buffering line -blocking 0

    # And then of course set up handlers for both pipes...

There are pros and cons. '/bin/cat' does not work on Windows, so the abstraction global catSig; $catSig must be used instead. This means we depend on catSig being set properly, something which is notoriously difficult for Alpha to get right... (See discussion on Pipes.Paths.) In this respect pipe is better (TclX does work in Windows doesn't it?) 'cat' is yet another external process to manage and keep track of, and polute the system with --- perhaps this is a completely irrelevant, but... The pipe solution seems cleaner.

If you don't need to have stdout and stderr separated --- i.e. you just want to capture every sort of signal produced by the external proc andthrow it into a console --- then you can get away with something simpler: simply do

 
    # Open the a pipe to capture both stdout and stderr:
    set mixedPipe [open "|foo |& cat" RDWR]
    fconfigure $mixedPipe -buffering line -blocking 0

Window pipes

It is very often the case that one would like to spawn an external process and redirect all its output to a specified window. The following pair of procs is a sort of minimal implementation of this idea --- it takes advantage of the pipe command from Extended Tcl:

 
    proc pipeToWindow { win } {
       new -n $win
       pipe out in
       fconfigure $out -buffering none -blocking 0   
       fconfigure $in -buffering none -blocking 0
       fileevent $out readable [list writeTo $win $out]
       return $in   
    }

    proc writeTo { win pipe } {
	insertText -w $win [read $pipe]
    }

Then it is used like this:

 
    set winPipe [pipeToWindow "My Window"]
    set fooPipe [open "|foo >&@ $winPipe" w]
    fconfigure $fooPipe -buffering none -blocking 0

Now there are a couple of problems with this simple version. First of all, we never got to know the name of the out pipe, so how are we supposed to ever close it again? (Of course we do know the name of the input end of the pipe, so we can close that one and then the output end will never fill up, but this is not satisfactory...) Furthermore, windows can be closed without notice, and then the pipe will be broken (or more precisely, an error will be raised because the pipe (still alive) cannot find the window it is supposed to write to). One solution to this last problem would be to let writeTo check if the window exists, and simply create it if it doesn't:

 
    proc writeTo { winName pipe } {
	if { [lsearch -exact [winNames] $winName] == -1 } {
	    new -n $winName
	}    
	insertText -w $winName [read $pipe]
    }

This will avoid those 'Broken Window' errors. Note also that this is natural behaviour, compared to redirected output: usually in unix if you redirect output to a file that doesn't exist then it is simply created.

However, if the user closes the window, his intention might really be to close the pipe and the external process --- after all we aim at the closest association between the pipe and the window. From this point of view it would be better to have the pipe closed when the window is closed. So we set up a close-hook. But how can we tell which pipe(s) and process(es) are linked to a given window? It seems that we need a global array storing this information... Now the interface set up so far only created the write-to-the-window pipe --- it has no chance to know what sort of processes have their output redirected later. So to make all this more coherent, we need a wrapper to the call open "|foo >&@ $winPipe" w and set up the link process-pipe and pipe-window in one go. We should rather define something like

 
    proc openProcessToWindow { app win } {
	global pipeWindows
	new -n $win
	pipe out in
	fconfigure $out -buffering none -blocking 0   
	fconfigure $in -buffering none -blocking 0
	fileevent $out readable [list writeTo $win $out]
	set appPipe [open "|$app >&@ $in" w]
	fconfigure $appPipe -buffering none -blocking 0
	lappend pipeWindows($win) $appPipe $in $out 
	return $appPipe
    }

Now this is the only command we need to use. It pipes the output of the new process to the specified window and records the names of the involved pipes in the global array pipeWindows.

Now we just need

 
    hook::register closeHook properClose

    proc properClose { win } {
	if { [lsearch -exact [array names pipeWindows] $win] != -1 } {
	    foreach pipe $pipeWindows($win) {
		close $pipe
	    }
	}
    }

Note that after all, now that we spawn the process and connect to the window at the same time, there is no longer any real need to have the window pipe existing independently of the process pipe. We might as well simplify openProcessToWindow to this:

 
    proc openProcessToWindow { app win } {
	global pipeWindows
	new -n $win
	set appPipe [open "|$app |& cat" RDWR]
	fconfigure $appPipe -buffering none -blocking 0
	fileevent $appPipe readable [list writeTo $win $appPipe]
	lappend pipeWindows($win) $appPipe
	return $appPipe
    }

(Here, just to emulate the previous version, we throw both stdout and stderr into the window, using the |& cat trick described above,

The ideas expressed above are incorporated in the general pipe API in development, described elsewhere...

Retrieved from http://alphatcl.sourceforge.net/wiki/pmwiki.php/Pipes/ErrorPipesAndWindowPipes
Page last modified on October 02, 2008, at 07:54 PM