Tcl - Reading characters from stdin without having to press enter in Tcl - tcl

I would like to read from stdin on a per character basis without the stdin being flushed. I could not find how to do that after tweaking for hours. Tcl always seems to wait for the channel to be flushed even in fconfigure stdin -blocking 0 -buffering none. Is that true? How would I otherwise approach this?
More explanation:
Imagine a Tcl program that makes its own prompt with some threads running code in the background. I would like this prompt to react to single keystrokes, for example: when you press 'p' (without pressing enter) the prompt reads that character and pauses the threads, when you press 'q' the prompt kills the threads and stops the program. The cleanest and closest solution is shortly demonstrated in the following code snippet.
proc readPrompt { } {
set in [ read stdin 1 ]
if { $in eq "q" } {
puts "Quitting..."
set ::x 1
} {
puts "Given unknown command $in"
}
}
fconfigure stdin -blocking 0 -buffering none
fileevent stdin readable { readPrompt }
vwait x
The result from running this is:
a
Given unknown command a
Given unknown command
After pressing the 'a', nothing happens. My guess is that the stdin is not flushed or something. Pressing enter or CTRL-d triggers the fileevent and the prompt then reads both the characters 'a' and 'enter'.
Ideally, I want the enter-press not to be needed. How could I accomplish this?
EDIT: I found this question and solution about a related use in Python: Determine the terminal cursor position with an ANSI sequence in Python 3 This is approximately the behaviour I'm looking for, but in Tcl.

If you have 8.7 (currently in alpha) then this is “trivial”:
fconfigure stdin -inputmode raw
That delivers all characters to you, without echoing them. (There's also modes normal and password, both of which preprocess the data before delivery and only one of which echoes.) You'll have to look after giving visual feedback to the user yourself, and be aware that all includes all characters usually only used for line editing purposes.
Otherwise, on Unixes (Linux, macOS) you do:
exec stty raw -echo <#stdin >#stdout
to switch the mode to the same config, and:
exec stty -raw echo <#stdin >#stdout
to switch back. (Not all Unixes need the input and output redirects, but some definitely do.)
Windows consoles have something similar in 8.7, but not in previous versions; a workaround might be possible using the TWAPI console support but that's a very low level API (and I don't know the details).

Related

How to use Tcl script in unix pipeline?

Want to use some custom function (written in tcl) in Unix pipeline ie grep patt file.rpt | tclsh summary.tcl. How to make tcl script to take output from the pipeline, process and out on the commandline as if a normal unix command?
This is very easy! The script should read input from stdin (probably with gets) and write output to stdout (with puts; this is the default destination).
Here's a very simple by-line filter script:
set lineCount 0
while {[gets stdin line] >= 0} {
incr lineCount
puts stdout "$lineCount >> $line <<"
}
puts "Processed $lineCount lines in total"
You probably want to do something more sophisticated!
The gets command has two ways of working. The most useful one here is the one where it takes two arguments (channel name, variable name), writes the line it has read from the channel into the variable, and returns the number of characters read or -1 when it has an EOF (or would block in non-blocking mode, or has certain kinds of problems; you can ignore these cases). That works very well with the style of working described above in the sample script. (You can distinguish the non-success cases with eof stdin and fblocked stdin, but don't need to for this use case.)

Separating stdout and stderr in Tcl drops all but last line of stderr

I'm not a Tcl programmer, but I need to modify a Tcl script that invokes an external command and tries to separate stdout and stderr. The following is a minimal example of how the script currently does this.
#!/usr/bin/tclsh8.4
set pipe [open "|cmd" r]
while {[gets $pipe line] >= 0} {puts $line}
catch "close $pipe" errorMsg
puts "$errorMsg"
Here, cmd is a an external command, and for the sake of this example, I will replace it with the following shell script. (I'm working on a Linux machine, but you can modify this to write to stdout and stderr however is appropriate for your system.)
#!/bin/sh -f
echo "A" > /dev/stdout
echo "B" > /dev/stdout
echo "C" > /dev/stderr
echo "D" > /dev/stderr
When I execute cmd, I get the following four lines as expected:
% ./cmd
A
B
C
D
However, when I execute my Tcl script, I get:
% ./test.tcl
A
B
D
This is an example of a more general phenomenon, which is that catch seems to swallow all but the last line of stderr.
To me, the "obvious" way to approach this is to try to mimic what is happening with stdout, which obviously works and prints all lines of the output. However, the current implementation is based on getting a Tcl channel by using open "|cmd", which requires running an external command. I can't figure out how to create a channel without opening an external command, and even if I could figure that out, there are subsequent issues with this approach. (How do I get the output of close into the channel? And if I need to open a new channel to get the output of each channel I am closing, then wouldn't I need an infinite number of channels?)
If anyone has any idea why errorMsg drops the initial lines or another approach that does not suffer from this problem, please let me know.
I know that this will come up, so I will say in advance that switching to Tcl 8.5 is probably not an option for me in the short term, since I do not control the environment in which this script is run.

expect: how to send an EOF to spawnd process

I have a program read from stdin and process it. ( like "tee /some/file" )
This program wait stdin end to exit itself.
If I spawn it from Expect, after I send many content, how to send an "EOF" to the program?
there is a close command in Expect, but it will also send a SIGHUP, and can not expect program output anymore.
Expect works (on non-Windows) by using a virtual terminal which the spawned program runs within. This means that you can do things by sending character sequences to simulate keys. In particular, the EOF control sequence is done with Ctrl+D, which becomes the character U+000004. The terminal processes this to turn it into a true EOF.
There's a few ways to write it, depending on which escape sequence you prefer, but one of these will work:
# Hexadecimal-encoded escape
send \x04
# Octal-encoded escape
send \004
# UNICODE escape (also hexadecimal)
send \u0004
# Generate by a command
send [format "%c" 4]
When Expect is using Tcl 8.6, these all generate the same bytecode so use whichever you prefer.

TCL an issue with stdin

Considering the following code:
puts "What show command would you like to execute?"
set cmd [gets stdin]
proc makeLC {str} {
puts "begin"
puts $str
set lStr [string tolower $str]
set lStr [string trim $lStr]
puts "after low and trim"
puts $lStr
set lenStr [string length $lStr]
for {set i 0} {$i < $lenStr} {incr i} {
puts [string index $lStr $i]
}
return $lStr
}
set lcmd [makeLC $cmd]
When a user types "test12345" then backspaces to display "test123" then adds "67" to finally display "test12367"
puts $lStr returns "test12367"
but the "for" loop will display "test12345 67" the spaces between "12345" and "67" I believe are "\b\b".
Why the inconsistancy?
and how do I ensure that when passing $lStr that "test12367" is assigned and "test12345 67" is not
Normally, Tcl programs on Unix are run in a terminal in “cooked” mode. Cooked mode terminals handle all the line editing for you; you can simply just read the finished lines as they are produced. It's very easy to work with cooked mode.
But you can also put the terminal into raw mode, where (typically) the application decides to handle all the key strokes directly itself. (It's usual to turn off echoing of characters at the same time so applications handle the output side as well as the input side.) This is what editors like vi and emacs do, and so does the readline library (used in many programs, like bash). Raw mode is a lot more fiddly to work with, but gives you much more control. Separately from this is whether what is typed is echoed so it can be seen; for example, there's also non-echoing cooked mode, which is useful for passwords.
In your case, it sounds very much like the terminal is in echoing raw mode (unusual!) and your application expects it to be in echoing cooked mode; you're getting the actual character sent from the keyboard when the delete key is pressed (or maybe the backspace key; there's a lot of complexity down there!) which is highly unusual. To restore sanity, do:
# No raw, Yes echo
exec stty -raw echo <#stdin >#stdout
There's something conceptually similar on Windows, but it works through totally different system calls.
Consider using tclreadline which wraps GNU readline providing full support for interactive command-line editing.
Another solution which relies on the presence of an external tool is to wrap the call to the Tcl shell in rlwrap:
rlwrap tclsh /path/to/script/file.tcl

Tcl flush command returns error

I've gotten this error from Tcl flush command:
error flushing "stdout": I/O error
Any ideas why it can happen? Also, the man page doesn't say anything about flush returning errors. Can the same error come from puts?
And ultimately: what do do about it?
Thank you.
By default Tcl uses line buffering on stdout (more on this later). This means whatever you puts there gets buffered and is only output when the newline is seen in the buffer or when you flush the channel. So, yes, you can get the same error from puts directly, if a call to it is done on an unbuffered channel or if it managed to hit that "buffer full" condition so that the underlying medium is really accessed.
As to "I/O error", we do really need more details. Supposedly the stdout of your program has been redirected (or reopened), and the underlying media is inaccessible for some reason.
You could try to infer more details about the cause by inspecting the POSIX errno of the actual failing syscall — it's accessible via the global errorCode variable after a Tcl command involving such a syscall signalized error condition. So you could go like this:
set rc [catch {flush stdout} err]
if {$rc != 0} {
global errorCode
set fd [open error.log w]
puts $fd $err\n\n$errorCode
close $fd
}
(since Tcl 8.5 you can directly ask catch to return all the relevant info instead of inspecting magic variables, see the manual).
Providing more details on how you run your program or whether you reopen stdout is strongly suggested.
A note on buffering. Output buffering can be controlled using fconfigure (or chan configure since 8.5 onwards) on a channel by manipulating its -buffering option. Usually it's set to line on stdout but it can be set to full or none. When set to full, the -buffersize option can be used to control the buffer size explicitly.