【问题标题】:How to clearerr of stdin in Tcl after ctrl+d?ctrl+d 后如何清除 Tcl 中的标准输入?
【发布时间】:2018-12-25 14:56:57
【问题描述】:

我最近在通过 EOF 后 asked a question about reopening stdin in C,现在希望在使用 Tcl 时具有相同的行为。

我似乎找不到一个 Tcl 命令来做 C clearerr 会做的事情。如何一次将 ctrl+d 传递给标准输入,然后从 Tcl 脚本“重新打开”标准输入? (使用 C 编译外部库是作弊!)

目前使用 Windows,因此使用 ctrl+z,但我认为它们的工作方式相似,在这种情况下不会有所作为。下面是一些示例代码:

set var {}; # declare var to hold the line
gets stdin var; # read a line
if {[string length $var]>0} {puts $var}; # print if read
if {[eof stdin]} { # if end-of-file reached
  puts {read from stdin was canceled. reopening just for fun}; # some debug message
  puts -nonewline "eof reached for stdin. enter something more to echo: "; flush stdout
  # clearerr() ???
  gets stdin var
  if {[string length $var]>0} {puts $var}
}

编辑:阅读fileevent 我相信我可以提出一个解决方案,让用户根本不输入 EOF 来在标准输入和 GUI 控制之间进行转换。

【问题讨论】:

  • set stdin [open /dev/tty r] 可能适用于 Linux。不知道是否可以在windows上重新打开stdin。

标签: tcl stdin


【解决方案1】:

如何一次将 ctrl+d 传递给标准输入,然后从 Tcl 脚本“重新打开”标准输入?

我不确定这种期望对于 Tcl POV 是否有意义。如果 [eof] 在通道上被捕获,则标准输入的 Tcl 通道不会关闭(除非使用 [close] 显式关闭,或者 Tcl 完全关闭),因此无需重新打开它。观看:

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; just continue working"
      # set ::DONE 1
  } elseif { [fblocked $f] } {
    # Read blocked.  Just return
  } else {
    # Something else
    puts "can't happen"
    set ::DONE 3
  }
}

fconfigure stdin -blocking false
fileevent stdin readable [list isReadable stdin]

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

这只是 Tcl 文档中的标准 sn-p,也用于 How to check if stdin is readable in TCL?。此外,How to restart stdin after Ctrl+D? 上您的问题的答案和 cmets 中的一些 cmets 也适用于 Tcl。使用 openseek stdin 0 end 查看 Brad 的评论,前提是标准输入的源是可搜索的。

【讨论】:

  • 编辑了代码示例。我的问题是,一旦stdin 退出eof,我就找不到将其重置为不eof 的方法。这就是clearerr 会做的事情,这在 tcl 文档中找不到。我的示例中的第二个 gets stdin var 不会阻塞,因为它仍然是 eof。
  • 对于我 (macOS),第二个 gets stdin var 块就好了。恐怕我无法在 Win 上进行验证。无论行为如何,阻塞或非阻塞都不应受到(内部)EOF 标志的影响。您必须提供有关设置的更多详细信息。在任何情况下,使用事件循环 (fileevent) 可能是更好的设计选择,但我无法从闪烁的几个细节中看出(GUI 与 CLI 等)
  • 此外:当您将[eof] 测试放在我的 sn-p 中的适当(重新进入)分支中时,您将看到 eof 标志(Tcl 维护的那个)将被重置.
  • 至于“我的示例中的第二个获取 stdin var 不会阻塞,因为它仍然是 eof”。事实证明,Windows 默认控制台的工作方式与 *nix 和 macOS 下的同级控制台不同(除非在原始模式下)。 [gets] 假定行缓冲,但在 Windows 默认控制台上,ctrl-z (eof) 不会随之产生换行符。因此,第二个 [gets] 没有显示任何效果。 (感谢 sebres @Tclers 聊天向我解释)。
  • 我还了解到,您可以在 *nix/macOS 上使用 ctrl-dd 体验相同的无换行行为...这将显示您在 Windows 上也遇到的(不需要的)行为环境。
【解决方案2】:

我相信我找到了解决此问题的纯 TCL 方法:将 EOF 字符更改为 Ctrl-Z 以外的其他字符,读取一个虚拟行(从输入缓冲区中删除 Ctrl-Z),然后重置EOF 字符返回到Ctrl-Z。总结在一个过程中:

proc clearEOF {} {
    fconfigure stdin -eofchar { "\x01" "" }
    gets stdin dummy
    fconfigure stdin -eofchar { "\x1a" "" }
}

\x01 的选择有些武断:基本上任何不太可能与Ctrl-Z 一起出现在输入缓冲区中的东西都应该这样做。

注意:这仅在带有 TCL 8.6.9 的 Windows 10 上进行了测试。


原始测试程序

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"

希望是在阅读了多行后,由 EOF 标记 (Ctrl-Z) 终止,然后您可以阅读另一行。在实践中,EOF-state没有被清除,第二次调用gets不等待输入,立即返回-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:

注意:尽管TCL documentation 包括(我的重点):

read ?-nonewline? fileID

从 fileID 中读取所有剩余的字节,并返回该字符串。如果设置了 -nonewline,则如果最后一个字符是换行符,则将丢弃它。 在执行读取命令之前清除任何现有的文件结束条件

set line [ read stdin ] 之类的东西替换gets 没有区别。两个命令都立即返回。多次重复任一命令没有区别:一旦 TCL(和/或 Windows1)认为我们已经达到 EOF,我们停留 EOF!


我的解决方案

经过一番尝试,尝试了 TCL 拥有的每个文件操作命令,我想出了以下内容:

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"

this 版本的输出确实 等待更多输入:

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

我对正在发生的事情的假设是更改 EOF 字符确实重置 EOF 状态(无论这种情况发生在“TCL”还是“Windows“我不确定)。使用不同的 EOF 标记,我们可以读取包含留在输入缓冲区中的 Ctrl-Z 的行。 (根据您在Ctrl-Z 的任一侧输入的内容,这通常还包含一个行尾标记)。处理完Ctrl-Z 后,我们可以将EOF 字符back 重置为Ctrl-Z 并照常继续从stdin 读取。


1微软 WSL GitHub 页面上的This issue 表明可能是 Windows 有问题:一旦缓冲区中的 Ctrl-Z 总是返回 EOF ,即使使用了clearerr()。我对“过去 30 年来 xplat 程序员的另一个祸根,Unix 上的 Ctrl-D 和 Windows 上的 Ctrl-Z 的工作方式不同。”的阅读是,虽然问题是针对 WSL, 问题在 Windows 本身。有趣的是,最终评论(在撰写本文时)声明“已在 Windows Insider Build 18890 中修复”,但可能仍需要致电 clearerr()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-07-19
    • 1970-01-01
    • 1970-01-01
    • 2017-09-07
    • 1970-01-01
    • 2021-01-06
    • 1970-01-01
    相关资源
    最近更新 更多