fileevent

fileevent , a built-in Tcl command, binds a script to a file event.

See Also

chan event
the modern way of writing [fileevent]. The arguments and semantics are identical.
A higher-level channel API

Synopsis

fileevent channelId readable ?script?
fileevent channelId writable ?script?

Description

This command is used to create file event handlers. A file event handler is a binding between a channel and a script, such that the script is evaluated whenever the channel becomes readable or writable. File event handlers are most commonly used to allow data to be received from another process on an event-driven basis, so that the receiver can continue to interact with the user while waiting for the data to arrive. (from fileevent manpage) A specific description of the relations between the notifier (at the heart of the event system) and I/O channels appears in a SourceForged description at http://tcl.sourceforge.net/c-api/notifier.html .

Examples

Tcl/Tk examples? , stackoverflow, 2008-10-03
Bryan Oakley provides a simple example of [fileevent] with some Tk buttons tied to some other programs.
client/server with fileevent
shows asynchronous read/write communication over a pipe with fileevent, in a single script.
Port scanning in tcl
PT: a demonstration of using fileevent writable
recursive fileevent accumulator
NEM: a technique to avoid global variables when maintaining state between event callbacks.

From a post by Paul Duffin: If you want a separate fileevent for stderr and stdout then you will have to use some form of pipe which you can fake by using [open |] on a simple cat.exe file. e.g.

set pipe [open |cat.exe r+]
fileevent $pipe readable .stderr.processing.

set process [open "|process.exe 2>@$pipe" r+]
fileevent $process readable .stdout.processing.

RS: NB: On *n*x machines, just say cat. On Windows machines, you have no cat.exe by default, but a number of helpful toolsets provide it, for instance the Cygnus suite.

DKF: This is because the underlying device on the other side of stdout and stderr, a terminal in normal circumstances, is the same device at the OS level. On the other hand, if you've got one redirected to a pipe and the other a socket (why? I dunno!) then you'll see a disconnect between the two. It all depends on configuration.


Can one use a similar trick to put input into 'process.exe', when it asks for it on stdin? If that isn't possible, what if I know that process.exe is going to ask me for me for some specific pieces of information (say, 'name <return> password <return>'). Could I pass that in with 'fileevent writable'? -- Vince.

CL: No--if I understand the question correctly. However, [exec] does support [exec $myprog << $text], which might be what you're after. It's a great convenience, worth learning, in any case.

RS: ... and if you want to send something to a process's stdin, depending on a prompt from that, it looks very much like a job for Expect...

Vince: given Expect doesn't exist on Windows, I was hoping that one could at least handle simple cases with [exec $myprog >& $outpipe <& $pipe], and appropriate fileevents on $pipe, $outpipe. (e.g. if I know that $myprog will prompt me with 'password:' couldn't I look for that in a fileevent attached to $outpipe and then immediately 'puts' the password into $pipe?)


Kevin Kenny wrote in comp.lang.tcl:

The usual pattern for reading asynchronously from a pipe is something like:

proc isReadable { f } {
    # The channel is readable; try to read it.
    set status [catch { gets $f line } result]
    if { $status != 0 } {
         # Error on the channel
         puts "error reading $f: $result"
         set ::DONE 2
    } elseif { $result >= 0 } {
         # Successfully read the channel
         puts "got: $line"
    } elseif { [eof $f] } {
         # End of file on the channel
         puts "end of file"
         set ::DONE 1
    } elseif { [fblocked $f] } {
         # Read blocked.  Just return
    } else {
         # Something else
         puts "can't happen"
         set ::DONE 3
    }
}
# Open a pipe
set fid [open "|ls"]

# Set up to deliver file events on the pipe
fconfigure $fid -blocking false
fileevent $fid readable [list isReadable $fid]

# Launch the event loop and wait for the file events to finish
vwait ::DONE

# Close the pipe
close $fid

Notice that the case

# Something else
puts "can't happen"
set ::DONE 3

diLampedusa: on Kenny's example CAN happen. gets can return -1 in non-blocking mode, if the input line was not yet completed with a end-of-line, and is a valid result.

NEM 2010-01-20: In this case, fblocked will return 1, so the code is correct -- the previous branch of the if will be selected.


mfi: Here is how filevent can be used to wait for user input for specified period of time.

proc gets-with-timeout {} {
    global GetsInput
    set id [after 1200 {puts "Are you fall asleep?"; exit}]
    fileevent stdin readable {gets stdin GetsInput}
    vwait ::GetsInput
    after cancel $id
    set data $GetsInput
    uplevel #0 "unset GetsInput"
    return $data
}

Setok Note that this is not really complete. What if other file events have been set up for the channel? You need to fetch the old fileevent and then reset it. See getsBG for how I tried this.


Arjen Markus: You should not be tempted to use [fileevent] to read from an ordinary file that is currently being written by another process. The reason (I learned this the hard way, including a longish thread of messages on the newsgroup) is that ordinary files are almost always readable. Hence the file events will trigger very rapidly and the processing of other events (in wish for instance) is compromised, effectively blocking your application. Instead look at Tailing widget for a better way.

LV: Indeed, this is one of Tcl's gotchas - a command called fileevent would be thought to work against files. It is unfortunate that some other name was not chosen. Current OSes don't support generating events as new data appears on a disk file. Instead, think of this command as being for open channels for pipes, sockets, etc. - things which have applications on the other end...

RJM 2004-07-30: Contrary to operation with ordinary files, [fileevent] provides its intended functionality on ports to the outside world, especially serial ports. That means: an event is triggered only when any characters are in the queue (for input). At this place I'd propose an extra option to fileevent:

  • -minsize n

This would trigger after having at least n characters in the queue. Such an option would support block based transfer and reduce software overhead. Another option would allow to specify a trigger when a certain character or character sequence matches:

  • -match string

With these options, implementing protocols with ASCII based end of message characters as well as binary fixed length messages would be supported better than now. Any checksum behind end of message characters could be taken into account with an offset after match.

Prior to launching a TIP, comments are appreciated.

Ok, a comment. filevent -match... A fifo channel that matches on a string. Isn't that Expect's 'expect' command?

DKF: Re -minsize, do be aware that the OS doesn't give you that info at all (well, assuming that you're not asking for a single byte).


CMcC would like to provide a little bit of esoterica. The manual states that an error from a script is handled by [bgerror] (good) and that the corresponding [fileevent] is deleted (also good.) However, in tcl8.5, one can return a -code which is not a TCL_ERROR, and this *also* leads to the deletion of the fileevent. Personally, I think the error handling is being a bit overzealous by treating any non-0 return code as an error.


lexfiend: It seems that fileevent can cause the Tcl event loop to stop working under circumstances that are not well-documented. For instance, I documented my experience in this comp.lang.tcl thread on trying to fileevent over 1021 TCP connections under Linux.

Update 2010-08-27: Further investigation reveals that the underlying use of select() on Unix platforms will trigger undefined behavior in the above scenario. See the above thread for more details on what happens under Linux. In short, ensuring that your Tcl program's open FD limit is capped at 1024 under Unix is a Good Thing.


jmc - 2012-08-01 13:31:44

What is the interest of creating a fileevent channelId readable where channelId represents a file on a disk? To my understanding this handler will *always* fire as the firing criteria is the presence of EOF character in the file (wich to my knowledge can't be absent in regular files - or am I wrong ?). I ask this question because I recently have to work on a simple way for an aplication to detect changes in the content of a text file. When changes occurs in this text file, then the application open a window to show the file's new content (the new content allways erase and replace the former content). I thought filevent command should be the good tool for this monitoring, but "filevent readable" fires when there is no change (but yes the file is readable). So instead I use repeated polling of file mtime on the file of interest.


arjen 2012-08-01 14:06:14:

For files on disk, [fileevent] is indeed not useable (so the chosen name is not quite apt). The solution you chose seems a reasonable one.

RLE 2012-08-01: If you are running under Linux you can use the tcl-inotify extension to watch, in an event driven manner, for changes to the file on disk.