pipe

This command is part of the TclX package.

pipe ?fileId_var_r fileId_var_w?

This TclX command allows one to create an unnamed pipe. If fileId_var_r and fileId_var_w are specified, then pipe will set the a variable named fileId_var_r to contain the fileId of the side of the pipe that was opened for reading, and fileId_var_w will contain the fileId of the side of the pipe that was opened for writing.

If the fileId variables are not specified, then a list containing the read and write fileIds is returned as the result of the command.

There is a proposal in tclx file commands for core to move this command into Tcl's source code. Could it alternatively be simulated using a reflected channel?


Note that beginning in version 8.6, Tcl provides the same functonality via its chan pipe command.

See also tclpipe for a backported external implementation of the pipe command for those not wanting TclX.


What is a pipe?

Note that the above mentioned Tclx command is a method of explicitly constructing the abstract notion of a computer pipe .

Tcl itself already has one means of interacting with a pipe. The Tcl open command has the ability to create pipes to commands.

So, what is a pipe? A pipe is a software construct whereby one builds a first-in first-out (FIFO) data container, into which one can write any sort of data, with the expectation that some other piece of code (perhaps even in another process) will read this data and be able to manipulate it.

The first place I (LV) ever saw pipes were on the first Unix system I ever used, with the Bourne shell. In that setup, programs frequently were written which would produce some sort of ascii newline terminated output, written to stdout, which would be connected to a pipe. One would then, on the other end of the pipe, one would run another process. The pipe would be attached to that program's stdin. An example might be:

  ls -l /etc | grep passwd

This command tells the shell to execute the ls command, with the -l and /etc arguments. The | symbol indicates to Bourne that the stdout of ls should be attached to the pipe. The notation also, simultaneously indicates that the bourne shell should start a second process, whose stdin is attached to that same pipe. In the second process, Bourne is to start the grep command with one argument - the string "passwd".

The result would be the output of zero or more files containing the letters "passwd" which reside in /etc .


Example code for Tclx's pipe command

Can somebody give an example for this? My attempt to use

 package require Tclx
 pipe rpipe wpipe;
 puts $wpipe "test\n";
 read $wpipe

simply leads to a lock up. Any help appreciated ;).


The problem is that read tries to read to the end of file. Since a pipe does not have an end of file indication ( unless one of the ends is closed) read never returns. An example with gets and an example with read are given below. In both the gets and read examples you must flush the pipe before anything will show up on the read end.

 package require Tclx
 pipe rpipe wpipe;
 puts $wpipe "test"
 flush $wpipe
 puts "[gets $rpipe]"

For the read example, supplying a large capture size to the read command will make it less likely you will have to loop more than once. Also critical to using read was using fconfigure to make the read pipe non-blocking.

 package require Tclx
 pipe rpipe wpipe;
 fconfigure $rpipe -blocking 0 
 puts $wpipe "test"
 flush $wpipe
 set buff ""
 set done 0 
 while { !$done } {
    set appstr [ read $rpipe 1000 ]
    append buff $appstr
    if { [ string length $appstr ] != 1000 } {
        set done 1;
    } 
 }
 puts "$buff"

Dealing with blocked pipes

<jkock, 2005-04-20> What you need to know is just that 'socket pairs' created by [pipe] by default are blocking with full buffering (size 4096) (just like pipes created with [open |]). This means that unless you are very careful with what you send through the pipe, and when you try to get something out in the other end, there is a fair chance that it will block and freeze your programme. E.g.

    % pipe O I
    % fconfigure $I
    -blocking 1 -buffering full -buffersize 4096 -encoding utf-8 \
       -eofchar {} -translation lf
    % puts $I horse
    % flush $I
    % gets $O
    horse
    % puts $I cow
    % flush $I
    % read $O
    --> HANGS

The first in-out operation was alright: we filled a line into the pipe; that's not enough to fill the buffer but we flushed it manually. Then there was a line ready to read in the output end, and we took it with [gets]. The second in-out operation made the interpreter hang: although we flushed the input end, the output end is still waiting for the buffer to fill up.

Or if for some reason you know in advance how many chars are in the pipeline, then you can just ask for those, and the interpreter will not hang:

    % puts $I "my favourite pillow"
    % flush $I
    % read $O 20
    my favourite pillow

    %

In most situations you don't know, so what you want is probably to have a nonblocking pipe. This avoids the problem:

    % fconfigure $O -blocking 0
    % puts $I "Charlie Parker"
    % flush $I
    % read $O
    Charlie Parker

    % 

If we didn't flush $I then the [read $O] command would just return an empty string. To avoid having to say flush all the time, we could furthermore do

    % fconfigure $I -buffering line

Then the input end is flushed automatically whenever there is a full line.

As a thumb rule: for the input end it doesn't matter so much if it is blocking or not, but adjusting the buffering is important. For the output end, the buffering is not so important (if just this is taken care of in the input end), but blocking/nonblocking is the crucial setting.

(To be on the safe side, of course you can just fconfigure both ends of the pipe as nonblocking with line buffering --- in those situations where some waiting is needed it is usually clear.)

Why use a pipe?

So what are such pipes good for, anyway? Why would you want to put data into one end just to pull it out in the other end?

Here are two situations:

* sometimes you need to have the name of a channel in advance

* sometimes you configure the two ends in different ways, to use the pipe as a sort of filter.

As an example of the first situation, suppose you open a pipe to some unix process using [open |] and want to redirect stderr. Well, for this you need a channel to redirect to! For example,

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

Now you can read the output from sh on $mainPipe and the errors on $errorPipe. There is a page on the Alpha Wiki giving some more details on this [1 ].

As an example of the second situation, suppose you want to read a certain number of bytes (not chars) from an utf-8 stream $U. Then you can first configure $U as a binary channel, read the bytes you want, put the result into your translation pipe whose input end is binary encoded and whose output end is utf-8 encoded. Like so:

    % fconfigure $U -encoding binary -translation binary
    % pipe R B
    % fconfigure $B -encoding binary -translation binary -buffering none
    % fconfigure $R -encoding utf-8 -blocking 0
    % puts -nonewline $B [read $U 6]  ;# read precisely 6 bytes
    % read -nonewline $R
    æøå
    # the 6 bytes represent 3 chars

This is described in more detail on the page indexing a flat file, and a readBytes proc.


Where is the point in using a pipe without forking or threading ?