I believe I have a found a pure-TCL way around this problem: change the EOF character to something other than Ctrl-Z
, read a dummy line (to remove the Ctrl-Z
from the input buffer) and then reset the EOF character back to Ctrl-Z
. Wrapped up in a procedure:
proc clearEOF {} {
fconfigure stdin -eofchar { "\x01" "" }
gets stdin dummy
fconfigure stdin -eofchar { "\x1a" "" }
}
The choice of \x01
is somewhat arbitrary: essentially anything that is not likely to be in the input buffer alongside the Ctrl-Z
should do.
Note: This has only been tested on Windows 10 with TCL 8.6.9.
Original Test Program
puts "Enter lines then Ctrl-Z <RETURN> to end"
while { [ gets stdin line ] >= 0 } {
puts "Read: $line"
}
puts "Reached EOF"
puts "eof=[eof stdin]"
puts "Enter another line"
puts "gets=[gets stdin line]"
puts "Read: $line"
The wish is that after having read a number of lines, terminated by the EOF-marker (Ctrl-Z
), you can then read another line. In practice, the EOF-state is not cleared, and the second call to gets
does not wait for input and immediately returns -1
(=EOF):
Enter lines then Ctrl-Z <RETURN>
Line1
Read: Line1
Line2
Read: Line2
^Z
Reached EOF
eof=1
Enter another line <-- This does not wait
gets=-1
Read:
Note: despite the TCL documentation including (my emphasis):
read ?-nonewline? fileID
Reads all the remaining bytes from fileID, and returns that string. If -nonewline is set, then the last character will be discarded if it is a newline. Any existing end of file condition is cleared before the read command is executed.
replacing the gets
with something like set line [ read stdin ]
makes no difference. Both commands return immediately. Having multiple repetitions of either command makes no difference: once TCL (and/or Windows1) thinks we've hit EOF, we stay at EOF!
My Solution
After some playing around, trying every file-manipulation command I could find that TCL posses, I came up with the following:
puts "Enter lines then Ctrl-Z <RETURN>"
while { [ gets stdin line ] >= 0 } {
puts "Read: $line"
}
puts "Reached EOF"
puts "eof=[eof stdin]"
puts "Reset EOF char"
fconfigure stdin -eofchar { "\x01" "" }
puts "eof=[eof stdin]"
puts "Reading dummy line"
puts "gets=[gets stdin line]"
fconfigure stdin -eofchar { "\x1a" "" }
puts "Enter another line"
puts "gets=[gets stdin line]"
puts "Read: $line"
The output of this version does wait for more input:
Enter lines then Ctrl-Z <RETURN>
Line 1
Read: Line 1
Line 2
Read: Line 2
^Z
Reached EOF
eof=1
Reset EOF char
eof=0 <-- EOF has been cleared
Reading dummy line
gets=1 <-- Read 1 character: the Ctrl-Z
Enter another line
More text <-- Waits for this to be typed
gets=9
Read: More text
My assumption of what's happening is that changing the EOF-character does reset the EOF status (whether this happens "in TCL" or "in Windows" I'm unsure). With a different EOF-marker in place, we can read the line containing the Ctrl-Z
that has been left in the input buffer. (Depending on what you entered either side of the Ctrl-Z
, this would normally also contain an end-of-line marker). With the Ctrl-Z
disposed of, we can reset the EOF-character back to Ctrl-Z
and carry on reading from stdin
as normal.
1 This issue on Microsoft's WSL GitHub page suggests that it could be Windows that is at fault: once the Ctrl-Z
in the buffer, it always returns EOF, even when clearerr()
is used. My reading of "Another bane for xplat programmers for the last 30 years, Ctrl-D on Unix and Ctrl-Z on Windows don't work the same." is that although the issue is against WSL, the problem is in Windows itself. Interestingly, the final comment (at time of writing) states "Fixed in Windows Insider Build 18890", but one might still need to call clearerr()
.