buffertclpipe

Simple program that reads and writes to a pipe


Although I am quite familiar with Tcl this is a beginner question. I would like to read and write from a pipe. I would like a solution in pure Tcl and not use a library like Expect. I copied an example from the tcl wiki but could not get it running.

My code is:

cd /tmp
catch {
    console show
    update
}

proc go {} {
    puts "executing go"

    set pipe [open "|cat" RDWR]
    fconfigure $pipe -buffering line -blocking 0
    fileevent $pipe readable [list piperead $pipe]

    if {![eof $pipe]} {
      puts $pipe "hello cat program!"
      flush $pipe
      set got [gets $pipe]
      puts "result: $got"

    }
}

go

The output is executing go\n result:, however I would expect that reading a value from the pipe would return the line that I have sent to the cat program.

What is my error?

--
EDIT:

I followed potrzebie's answer and got a small example working. That's enough to get me going. A quick workaround to test my setup was the following code (not a real solution but a quick fix for the moment).

cd /home/stephan/tmp
catch {
    console show
    update
}

puts "starting pipe"
set pipe [open "|cat" RDWR]
fconfigure $pipe -buffering line -blocking 0
after 10
puts $pipe "hello cat!"
flush $pipe

set got [gets $pipe]
puts "got from pipe: $got"

Solution

  • Writing to the pipe and flushing won't make the OS multitasking immediately leave your program and switch to the cat program. Try putting after 1000 between the puts and the gets command, and you'll see that you'll probably get the string back. cat has then been given some time slices and has had the chance to read it's input and write it's output.

    You can't control when cat reads your input and writes it back, so you'll have to either use fileevent and enter the event loop to wait (or periodically call update), or periodically try reading from the stream. Or you can keep it in blocking mode, in which case gets will do the waiting for you. It will block until there's a line to read, but meanwhile no other events will be responded to. A GUI for example, will stop responding.

    The example seem to be for Tk and meant to be run by wish, which enters the event loop automatically at the end of the script. Add the piperead procedure and either run the script with wish or add a vwait command to the end of the script and run it with tclsh.

    PS: For line-buffered I/O to work for a pipe, both programs involved have to use it (or no buffering). Many programs (grep, sed, etc) use full buffering when they're not connected to a terminal. One way to prevent them to, is with the unbuffer program, which is part of Expect (you don't have to write an Expect script, it's a stand-alone program that just happens to be included with the Expect package).

    set pipe [open "|[list unbuffer grep .]" {RDWR}]