Version 11 of Tail

Updated 2006-06-25 17:01:21

Comp.lang.tcl questioners frequently demand some sort of gadget to monitor-an-ongoing-process-and-display-the-result-in-a-text. Tailing widget is the simplest answer CL knows that meets the common intent of these requests.

There are lots of variations on this theme, though. tailf carefully implements the functionality of the Unix tail(1), and adds a bit of filtering capability. I've written a few examples [L1 ] that show how different combinations of IPC, fileevent, after, ... can co-operate to monitor continuing processes.

A utility to filter the output of tail is greptail. It lazily uses the unix tail(1) program.

Also see directory notification package in Tcl (Linux 2.4 and higher).

And a more generic approach is outlined on the file and directory change notifications page.

Here is another version. It continuously opens and closes the file and seems to work where a normal "tail -f" fails. This is the case at my internet provider's ssh-account (Linux infong159 2.4.24-grsec-20040109a). Issuing a "tail -f access_log" on the shell there will produce no output when I access the homepage, but using this little proc, I get instant response (well, within a second at least). I don't think this is a logfile-rotation issue, though:

 proc tail-f {filename} {
    # get an initial last position:
    set fh [open $filename r]
    seek $fh 0 end
    set pos [tell $fh]
    close $fh
    # ask again all second:
    while 1 {
         after 1000
         set fh [open $filename r]
         fconfigure $fh -blocking no -buffering line
         seek $fh $pos start
         while {[eof $fh] == 0} {
             gets $fh line
             if {[string length $line] > 0} {puts $line}
        }
        set pos [tell $fh]
        close $fh
    }
 }

[MJ] To use this in a GUI (in the example above a call to tail-f will block) you can use the following:

 proc tailf filename {
     # if toplevel already exists wait 2 seconds before retrying
     set now [clock seconds]
     set tl .$now
     if {[info commands $tl]!=""} {
       after 2000 [list tailf $filename]
       return
     }
     toplevel $tl
     wm title $tl $filename 
     text $tl.t -width 60 -height 10 -yscrollcommand "$tl.scroll set"
     scrollbar $tl.scroll -command "$tl.t yview"
     pack $tl.scroll -side right -fill y
     pack $tl.t -side left

     set fp [open $filename]
     seek $fp 0 end
     set currpos [tell $fp]
     close $fp
     set mtime [file mtime $filename]
     read_more ${tl}.t $filename $mtime $currpos
   }

   proc read_more {widget filename oldmtime oldpos } {
       # check if the tail widget is not closed
       if {[info commands $widget] eq ""} {
         # tail window closed
         return
       } 
       set mtime [file mtime $filename]
       set currpos $oldpos
       if {$mtime != $oldmtime} {
         set fd [open $filename r]
         seek $fd $oldpos start
         while {![eof $fd]} {
           $widget insert end [read $fd]
         }
         $widget see end
         set currpos [tell $fd]
         close $fd
       }
       after 1000 [list read_more $widget $filename $mtime $currpos]
   }

 # example usage
 tailf c:/test.log
 tailf c:/test2.log