** Synopsis ** === [package require] Tcl 8.6 [lassign] [['''chan pipe''']] readChanId writeChanId ... [close] $readChanId; [close] $writeChanId === ** Description ** Creates a standalone anonymous ''operating system'' pipe and returns a pair of channel handles for its read and write ends, in this order. The most useful way to use such pipes is to redirect standard channel(s) of a process to be run using [[exec ... &]] in which case it becomes possible to close the stdin of the spawned process forcing it to detect EOF and exit while collecting what it writes on its stdout/stderr. For instance, the [tclgpg] extension makes heavy use of this technique to control the '''gpg''' binary being executed. This subcommand was introduced by [TIP] #304 [http://tip.tcl.tk/304.html] and is mostly a rip of the [pipe] command implementation from [Tclx]. ** Availability ** The implementation is available in Tcl ≥ 8.6. [kostix] has created a TEA-based extension out of this implementation called [tclpipe]. It can be used with (supposedly) any version of Tcl (tested on 8.4 and 8.5). ** Should This Work? ** [PYK] 2015-04-18: The following code hangs at `[read] $pr1`. Is it coded wrong, is it a bug, or is it a "don't do that" scenario? ====== #! /bin/env tclsh package require Thread lassign [chan pipe] pr1 pw1 lassign [chan pipe] pr2 pw2 set tid [thread::create] thread::transfer $tid $pw1 thread::transfer $tid $pw2 thread::send $tid [ list exec [info nameofexecutable] << { for {set i 0} {$i < 10000} {incr i} { puts hello puts stderr goodbye } } >@$pw1 2>@$pw2 & ] thread::send $tid [list close $pw1] thread::send $tid [list close $pw2] puts [list reading $pr1] set out [read $pr1] puts done puts [string length $out] close $pr1 set errout [read $pr2] puts [string length $errout] close $pr2 ====== [DKF]: The problem is that the `puts stderr` is in the loop, which fills up that pipe and stops things from working. It's important to use asynchronous processing (which Tcl is ''good'' at!) when working with multiple pipes so that neither blocks. Try this version instead: ====== package require Thread lassign [chan pipe] pr1 pw1 lassign [chan pipe] pr2 pw2 set tid [thread::create] thread::transfer $tid $pw1 thread::transfer $tid $pw2 thread::send $tid { proc doStuff {out err} { exec [info nameofexecutable] << { for {set i 0} {$i < 10000} {incr i} { puts hello puts stderr goodbye } } >@ $out 2>@ $err & close $out close $err } } thread::send $tid [list doStuff $pw1 $pw2] fileevent $pr1 readable "readpipe $pr1 $pr2" fconfigure $pr1 -blocking 0 fileevent $pr2 readable "readpipe $pr2 $pr1" fconfigure $pr2 -blocking 0 proc readpipe {p1 p2} { append ::out($p1) [read $p1] if {[eof $p1]} { close $p1 if {$p2 ni [chan names]} { set ::done ok } } } puts [list reading $pr1] vwait done puts [string length $out($pr1)] set errout $out($pr2) puts [string length $errout] ====== <> [aspect]: Sounds similar in application to [String channels], which works on 8.4/5 at least. [kostix] comments: [aspect], I doubt this: pipes [[chan pipe]] creates are ''operating system objects'' which have real handles hidden behind Tcl channels. So when you, for instance, redirect the stdin of a spawned process to the pipe, on the OS level the handle of the process stdin will be the read handle of the OS pipe. And this is the OS who transports the data over such a pipe between Tcl channels. [aspect]: Right you are .. string channels are a much more simple-minded facility, and not nearly as useful. ---- [AMG]: [[chan pipe]] can be used to access the [stderr] of an [[[open] |]] [pipeline]. ====== lassign [chan pipe] rderr wrerr set stdio [open |[concat $command [list 2>@ $wrerr]] a+] # write to $stdio -> pipeline's stdin # read from $stdio <- pipeline's stdout # read from $rderr <- pipeline's stderr ====== [PYK] 2015-04-13: I think it's also necessary to `[close] $wrerr`, so that `eof` can get generated on `$rderr` when the executed command finishes: ====== lassign [chan pipe] rderr wrerr set stdio [open |[concat $command [list 2>@ $wrerr]] a+] close $wrerr # write to $stdio -> pipeline's stdin # read from $stdio <- pipeline's stdout # read from $rderr <- pipeline's stderr close $rderr ====== <> ** See Also ** * [a way to 'pipe' from an external process into a text widget] <> Command | Channel