Version 2 of bexec

Updated 2014-05-12 01:02:50 by RLE

How to do binary-safe "exec":

proc bexec {command input} {
    # Execute shell "command", send "input" to stdin, return stdout.
    # Ignores stderr (but "2>@1" can be part of "command").
    # Supports binary input and output. e.g.:
    #     set flac_data [bexec {flac -} $wav_data]

    # Run "command" in background...
    set f [open |$command {RDWR BINARY}]
    fconfigure $f -blocking 0

    # Connect read function to collect "command" output...
    set ::bexec_done.$f 0
    set ::bexec_output.$f {}
    fileevent $f readable [list bexec_read $f]

    # Send "input" to command...
    puts -nonewline $f $input
    close $f write

    # Wait for read function to signal "done"...
    vwait ::bexec_done.$f

    # Retrieve output...
    set result [set ::bexec_output.$f]
    unset ::bexec_output.$f
    unset ::bexec_done.$f

    fconfigure $f -blocking 1
    try {
        close $f
    } trap {CHILDSTATUS} {options info} {
        dict set info -errorinfo $result
        return -options $info $result
    }

    return $result
}


proc bexec_read {f} {
    # Accumulate output in ::bexec_output.$f.

    append ::bexec_output.$f [read $f]
    if {[eof $f]} {
        fileevent $f readable {}
        set ::bexec_done.$f 1
    }
}

PYK 2014-05-11: The code above does not need to go to the effort of setting the channel to non-blocking, as Tcl will manage the buffer behind the scenes. Forget about setting the channel to non-blocking, dispense with the vwait, send the desired data into the channel, close the write side of the channel, and then read the output. For code that does need to use non-blocking channels, it's generally best not to interfere with the event loop by using vwait as the code above does. Instead, consider structuring the code such that it works in an event-oriented manner. See also Example of reading and writing to a piped command, which provides a template for conducting an interactive conversation with another process.