Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations IamaSherpa on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

fileevent problem

Status
Not open for further replies.

fabien

Technical User
Sep 25, 2001
299
AU
A new problem:

I have the following command:
set command "cd $dir; cat toto" where toto is a file

then

if [catch {open "|$command |& cat"} input ] {
$log insert end $input\n
} else {
fileevent $input readable [Addtext $input]
}

Where Addtext is a function that simply adds text into log (text widget)

In my widget I get "file6" only so I would expect to have the output of the command I set, what am I doing wrong?

 
The problem is the line:

Code:
fileevent $input readable [Addtext $input]

The square brackets cause immediate evaluation of the command "Addtext $input", and the value of input is the channel identifier, in the case, "file6".

What I suspect you wanted instead was:

[tt]fileevent $input readable [ignore][[/ignore]list Addtext $input[ignore]][/ignore][/tt]

This executes the list command to build up a 2-element list consisting of "Addtext" and the value of input. The resulting list is then registered as the callback command for your fileevent.

This technique is often used when creating callbacks to force immediate variable substitution, but to preserve argument boundaries. Some people mistakenly (or lazily) use "" instead. This would have worked in your case:

Code:
fileevent $input readable "Addtext $input"

because the value of input has no whitespace characters or other characters special to the Tcl interpreter. But the following would fail:

Code:
set msg "Read a line"
fileevent $input readable "puts $msg"

In this case, Tcl performs substitution between the quotes, but does simple string concatenation on the results. So, the callback registered in this case would be:

[tt]puts Read a line[/tt]

When this callback executes, you'll get an error that puts received too many arguments. On the other hand:

Code:
set msg "Read a line"
fileevent $input readable [list puts $msg]

works, because list creates a 2-element list:

[tt]puts {Read a line}[/tt]

which is a well-formed Tcl command. - Ken Jones, President
Avia Training and Consulting
866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
You can try:
Code:
  proc Addtext {}   { 
    if {[eof $::input]} { close $::input }     else { .t insert end [gets $::input] }
  }
  set rc [catch { set ::input [open "|$command"] } errmsg]
  if {$rc}   { .t insert end "***error: $errmsg\n" }   else   { fileevent $input readable Addtext }
The last parameter of fileevent is the name of a script.
This script must deal with end of file.

Bonne chance

ulis
 
Thanks for this guys, now I have another problem. I put the above statements in a loop like this


while xxx
Runone()


and Runeone contains somthing like
set command xxx
set rc [catch { set ::input [open "|$command"] } errmsg]
if {$rc} { .t insert end "***error: $errmsg\n" } else { fileevent $input readable Addtext }

The problem is that before the first command is finished the next one starts and this causes problems as I am using the same filename as input (to save space)and the same outputfile (the result will be merged with the previous file). How can I "force" each Runone to completly finish before starting the new one?
 
Well, the easiest way would be to simply exec your commands instead of using open to create a pipe to the command. By default, exec blocks until the command finishes, so you could just use several exec commands to run your programs in sequence.

However, this isn't an acceptable solution when you've got a GUI program, because while exec is blocking, Tcl can't get into the event loop to process events. The effect is that your application is locked until the program finishes, which annoy users no end.

The best way to handle this in pure Tcl will be to change your program so that it doesn't use a while loop to start all the programs. Instead, one strategy is to write a procedure that somehow takes a command list and starts only one of them at a time. We'll then arrange to keep calling this procedure each time a command finishes. You know when the command for an open pipe ends because you get an EOF condition on your pipe. (This is usually a safe assumption, although there are a few programs out there that close their output channels and keep on executing.) You should already be testing for EOF in your fileevent handlers, and closing the pipe in response. EOF is a persistant readable event, so if you don't close the pipe in response, your fileevent handle immediately gets called again and again in an endless loop.)

There are several different implementations you could use. Here's a simple (untested) one that comes to mind. We'll use a global variable called cmdList to store a list of commands to execute in sequence. We'll create a procedure called RunOne that starts the first command on the list, and then removes it from the list. If cmdList is empty, RunOne simply returns. We'll call RunOne once to start the sequence of execution. Then we'll call RunOne again whenever we get an EOF in our fileevent handler.

Code:
proc RunOne {} {
  global cmdList
  global input

  # As long as there are commands to run,
  # start the next one.

  if {[llength $cmdList] != 0} {

    # Start the command

    if {[catch {open "|$command"} input]} {
      .t insert end "***error: $errmsg\n"
    } else {
      fileevent $input readable Addtext
    }

    # Remove it from the list

    set cmdList [lreplace $cmdList 0 0]
  }
}

proc Addtext {} {
  global input

  # Read a line and process it

  if {[catch {gets $input line} num]} {

    # We got an error trying to read a line.
    # Close the channel and start the next
    # command.

    catch {close $input}

    # Do whatever error handling you want here

    RunOne

  } elseif {$num == -1} {

    # We received EOF. Close the channel and
    # start the next command.

    catch {close $input}

    # Do whatever EOF handling you want here

    RunOne

  } else {

    # We simply read a line of text. Do
    # whatever you need to do.

  }
}

# Set up the command list and start the
# first command in the list.

set cmdList [list]
lappend cmdList "The first command to run"
lappend cmdList "The second command to run"
lappend cmdList "The third command to run"
RunOne

Another possibility would be to use the bgexec command from the BLT extension. bgexec allows you to start a program using very similar syntax to exec. However, bgexec can run the program in the background -- so your application can still handle events -- while still capturing the output of the command. You can also receive notification when the command exits. You can find out more information about BLT on the Tcl'ers Wiki ( on the page "BLT," from the BLT homepage, and from the BLT SourceForge page, - Ken Jones, President
Avia Training and Consulting
866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
Thanks Avia, I actually did a variant of yours and it worked fine. Now the problem is that the text widget (where I write the output from the open command)is not updated everytime a line is written but when the first command is finished. I would like to see what is going on while the process is running.. How can I force this?

Thanks!
 
More info, the output of the command is like:
*** Execution Phase started at 13:29:44
** .INPUT : Opening Tape-on-Disk file xxtmp.tod
.INPUT IPHYSR= 1
** .INPUT : First trace on Tape-on-Disk file xxtmp.tod :
.INPUT : LINE= 59.000 TRACE= 100.00 IPHYSR= 3
T=00:00:00 LINE= 59.000 TRACE= 100.00 KINCNT= 1
** .INPUT : End-of-File detected. Last trace read =
.INPUT : LINE= 129.00 TRACE= 71.000 IPHYSR= 4257
** .INPUT : End-of-Tape detected. Last trace read =
.INPUT : LINE= 129.00 TRACE= 71.000 IPHYSR= 4258
.INPUT : LINE= 129.00 TRACE= 71.000 IPHYSR= 4258
*** Batch Control Monitor: run xxxtmp ended normally at 13:29:49

and one line is normally displayed every second or so. If there some kind of buffer that stores all the output and release it when the job is finished?
 
More info, the output of the command is like:
*** Execution Phase started at 13:29:44
** .INPUT : Opening Tape-on-Disk file xxtmp.tod
.INPUT IPHYSR= 1
** .INPUT : First trace on Tape-on-Disk file xxtmp.tod :
.INPUT : LINE= 59.000 TRACE= 100.00 IPHYSR= 3
T=00:00:00 LINE= 59.000 TRACE= 100.00 KINCNT= 1
** .INPUT : End-of-File detected. Last trace read =
.INPUT : LINE= 129.00 TRACE= 71.000 IPHYSR= 4257
** .INPUT : End-of-Tape detected. Last trace read =
.INPUT : LINE= 129.00 TRACE= 71.000 IPHYSR= 4258
.INPUT : LINE= 129.00 TRACE= 71.000 IPHYSR= 4258
*** Batch Control Monitor: run xxxtmp ended normally at 13:29:49

and one line (i.e., .INPUT xxx) is normally displayed every second or so. If there some kind of buffer that stores all the output and release it when the job is finished?
 
Your guess about buffering was right on target. And there might not be anything you can do about it...

Virtually all programs use the standard I/O libraries. When a program interacts with a user on a terminal, the libraries typically perform line buffering, where they send to the terminal each complete line as it becomes available. However, they operate differently when communicating with another program. Typically, they use much larger buffers for output (usually around 4KB in size), because it is more efficient to pass around larger blocks of data between processes. So, in your script, you're not getting the information until either the buffer is filled or the program you open exits.

If you have access to the other program's source, you can flush the output buffer when desired. The Tcl command to do so is flush, and the C system call is fflush(). But you might not have access to the other program's source, or you might not want to go mucking about in it even if you do. In which case, you're stuck. When your program establishes contact with another program, there's no way for your program to force the other program to flush its output buffer. Except...

This problem is one of the reasons Expect was written. Expect fools the other program into believing that it's talking to real person sitting at a terminal, and so it uses line buffering for its output. Expect is actually a Tcl extension, and it adds commands for spawning another program under the control of your script, and then sending messages to that program and expecting responses. It's a very slick, sophisticated, and reliable tool. The only drawback to Expect is that it's officially supported only on Unix platforms. There is an unofficial, unmaintained, mostly functional port of Expect to Windows, but it's based on a much older version of Expect and it doesn't have quite all of the features implemented. If you really need to use it on Windows, you can go ahead and try it. There are actually quite a few pople out there happily using it on Windows, but there's no guarantee that it will work for you (and nobody to complain to if it doesn't).

You can find out more about Expect at the Tcl'ers Wiki ( on the page "Expect," and on the official Expect web site, - Ken Jones, President
Avia Training and Consulting
866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
Thanks very much, as I don't have access to the other code I'll have to check out Expect.

Another thing is that the other program when it runs writes out an ASCII log file while running, I could do a tail -f of this file. The problem is that I have to do the tail after the program is finished, unless I can run two processes at once, it it possible?
 
Yes, a Tcl script can run as many programs simultaneously as your system allows. Just keep opening them like you have, but remember to store the channel IDs returned by open in seperate variables. Then you can have a separate fileevent handler on each one to detect readable data, gets from a particular program, etc.

But the problem of "tailing" a file is trickier than it sounds at first. Check out the Tcl'ers Wiki page "Tail," for more information on doing this. - Ken Jones, President
Avia Training and Consulting
866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top