1

tclsh是一个包含 TCL 命令的 shell。

TCLuplevel命令评估给定的 TCL 脚本,但它无法评估 tclsh 脚本(可以包含 bash 命令)。

如何获得upleveltclsh 脚本的类似物?


考虑这个 TCL 脚本:

# file main.tcl

proc prompt { } \
{
   puts -nonewline stdout "MyShell > "
   flush stdout
}

proc process { } \
{
   catch { uplevel #0 [gets stdin] } got
   if { $got ne "" } {
       puts stderr $got
       flush stderr
   }
   prompt
}

fileevent stdin readable process

prompt
while { true } { update; after 100 }

这是一种 TCL shell,所以当你键入tclsh main.tcl它时会显示一个提示MyShell >,它就像你在交互式 tclsh会话中一样。但是,您处于非交互式 tclsh会话中,并且您键入的所有内容都由uplevel命令评估。所以在这里你不能像在交互式tclsh 会话中那样使用 bash 命令。例如,您不能vim直接从外壳打开,也exec vim无法正常工作。

我想要的是使MyShell >行为像交互式tclsh会话。我不能只使用的原因tclsh是最后一行的循环main.tcl:我必须有那个循环,一切都必须在那个循环中发生。我还必须在该循环的每次迭代中做一些事情,所以可以使用vwait.


这是解决方案。 我没有找到更好的解决方案来覆盖该::unknown功能。

# file main.tcl

    proc ::unknown { args } \
    {

        variable ::tcl::UnknownPending
        global auto_noexec auto_noload env tcl_interactive

        global myshell_evaluation
        if { [info exists myshell_evaluation] && $myshell_evaluation } {
            set level #0
        }  else {
            set level 1
        }

        # If the command word has the form "namespace inscope ns cmd"
        # then concatenate its arguments onto the end and evaluate it.

        set cmd [lindex $args 0]
        if {[regexp "^:*namespace\[ \t\n\]+inscope" $cmd] && [llength $cmd] == 4} {
        #return -code error "You need an {*}"
            set arglist [lrange $args 1 end]
        set ret [catch {uplevel $level ::$cmd $arglist} result opts]
        dict unset opts -errorinfo
        dict incr opts -level
        return -options $opts $result
        }

        catch {set savedErrorInfo $::errorInfo}
        catch {set savedErrorCode $::errorCode}
        set name $cmd
        if {![info exists auto_noload]} {
        #
        # Make sure we're not trying to load the same proc twice.
        #
        if {[info exists UnknownPending($name)]} {
            return -code error "self-referential recursion in \"unknown\" for command \"$name\"";
        }
        set UnknownPending($name) pending;
        set ret [catch {
            auto_load $name [uplevel $level {::namespace current}]
        } msg opts]
        unset UnknownPending($name);
        if {$ret != 0} {
            dict append opts -errorinfo "\n    (autoloading \"$name\")"
            return -options $opts $msg
        }
        if {![array size UnknownPending]} {
            unset UnknownPending
        }
        if {$msg} {
            if {[info exists savedErrorCode]} {
            set ::errorCode $savedErrorCode
            } else {
            unset -nocomplain ::errorCode
            }
            if {[info exists savedErrorInfo]} {
            set ::errorInfo $savedErrorInfo
            } else {
            unset -nocomplain ::errorInfo
            }
            set code [catch {uplevel $level $args} msg opts]
            if {$code ==  1} {
            #
            # Compute stack trace contribution from the [uplevel].
            # Note the dependence on how Tcl_AddErrorInfo, etc. 
            # construct the stack trace.
            #
            set errorInfo [dict get $opts -errorinfo]
            set errorCode [dict get $opts -errorcode]
            set cinfo $args
            if {[string bytelength $cinfo] > 150} {
                set cinfo [string range $cinfo 0 150]
                while {[string bytelength $cinfo] > 150} {
                set cinfo [string range $cinfo 0 end-1]
                }
                append cinfo ...
            }
            append cinfo "\"\n    (\"uplevel\" body line 1)"
            append cinfo "\n    invoked from within"
            append cinfo "\n\"uplevel $level \$args\""
            #
            # Try each possible form of the stack trace
            # and trim the extra contribution from the matching case
            #
            set expect "$msg\n    while executing\n\"$cinfo"
            if {$errorInfo eq $expect} {
                #
                # The stack has only the eval from the expanded command
                # Do not generate any stack trace here.
                #
                dict unset opts -errorinfo
                dict incr opts -level
                return -options $opts $msg
            }
            #
            # Stack trace is nested, trim off just the contribution
            # from the extra "eval" of $args due to the "catch" above.
            #
            set expect "\n    invoked from within\n\"$cinfo"
            set exlen [string length $expect]
            set eilen [string length $errorInfo]
            set i [expr {$eilen - $exlen - 1}]
            set einfo [string range $errorInfo 0 $i]
            #
            # For now verify that $errorInfo consists of what we are about
            # to return plus what we expected to trim off.
            #
            if {$errorInfo ne "$einfo$expect"} {
                error "Tcl bug: unexpected stack trace in \"unknown\"" {}  [list CORE UNKNOWN BADTRACE $einfo $expect $errorInfo]
            }
            return -code error -errorcode $errorCode  -errorinfo $einfo $msg
            } else {
            dict incr opts -level
            return -options $opts $msg
            }
        }
        }

        if { ( [info exists myshell_evaluation] && $myshell_evaluation ) || (([info level] == 1) && ([info script] eq "")  && [info exists tcl_interactive] && $tcl_interactive) } {
        if {![info exists auto_noexec]} {
            set new [auto_execok $name]
            if {$new ne ""} {
            set redir ""
            if {[namespace which -command console] eq ""} {
                set redir ">&@stdout <@stdin"
            }
            uplevel $level [list ::catch  [concat exec $redir $new [lrange $args 1 end]]  ::tcl::UnknownResult ::tcl::UnknownOptions]
            dict incr ::tcl::UnknownOptions -level
            return -options $::tcl::UnknownOptions $::tcl::UnknownResult
            }
        }
        if {$name eq "!!"} {
            set newcmd [history event]
        } elseif {[regexp {^!(.+)$} $name -> event]} {
            set newcmd [history event $event]
        } elseif {[regexp {^\^([^^]*)\^([^^]*)\^?$} $name -> old new]} {
            set newcmd [history event -1]
            catch {regsub -all -- $old $newcmd $new newcmd}
        }
        if {[info exists newcmd]} {
            tclLog $newcmd
            history change $newcmd 0
            uplevel $level [list ::catch $newcmd  ::tcl::UnknownResult ::tcl::UnknownOptions]
            dict incr ::tcl::UnknownOptions -level
            return -options $::tcl::UnknownOptions $::tcl::UnknownResult
        }

        set ret [catch {set candidates [info commands $name*]} msg]
        if {$name eq "::"} {
            set name ""
        }
        if {$ret != 0} {
            dict append opts -errorinfo  "\n    (expanding command prefix \"$name\" in unknown)"
            return -options $opts $msg
        }
        # Filter out bogus matches when $name contained
        # a glob-special char [Bug 946952]
        if {$name eq ""} {
            # Handle empty $name separately due to strangeness
            # in [string first] (See RFE 1243354)
            set cmds $candidates
        } else {
            set cmds [list]
            foreach x $candidates {
            if {[string first $name $x] == 0} {
                lappend cmds $x
            }
            }
        }
        if {[llength $cmds] == 1} {
            uplevel $level [list ::catch [lreplace $args 0 0 [lindex $cmds 0]]  ::tcl::UnknownResult ::tcl::UnknownOptions]
            dict incr ::tcl::UnknownOptions -level
            return -options $::tcl::UnknownOptions $::tcl::UnknownResult
        }
        if {[llength $cmds]} {
            return -code error "ambiguous command name \"$name\": [lsort $cmds]"
        }
        }
        return -code error "invalid command name \"$name\""

    }


proc prompt { } \
{
    puts -nonewline stdout "MyShell > "
    flush stdout
}

proc process { } \
{
    global myshell_evaluation
    set myshell_evaluation true
    catch { uplevel #0 [gets stdin] } got
    set myshell_evaluation false
    if { $got ne "" } {
        puts stderr $got
        flush stderr
    }
    prompt
}

fileevent stdin readable process 

prompt
while { true } { update; after 100 }

这个想法是修改::unknown函数,以便它处理MyShell评估作为tclsh交互式会话的评估。

这是一个丑陋的解决方案,因为我正在修复::unknown对于不同系统和不同版本的 tcl 可能不同的函数代码。

是否有任何解决方案可以规避这些问题?

4

4 回答 4

1

uplevel 不仅评估脚本,而且在执行脚本的实例的调用者的堆栈上下文中评估它。这是一个非常高级的命令,当您定义自己的执行控制结构时应该使用它,而 OFC 它是特定于 TCL 的——我发现自己无法想象 tclsh 等效项应该如何工作。

如果您只想评估另一个脚本,正确的 TCL 命令将是 eval。如果其他脚本是 tclsh,为什么不直接打开另一个 tclsh?

于 2012-01-04T21:13:54.947 回答
0

我认为最简单的答案是使用您正在使用的方法;重写unknown命令。具体来说,其中有一行检查以确保当前上下文是

  • 不在脚本中运行
  • 交互的
  • 在顶层

如果您替换该行:

if {([info level] == 1) && ([info script] eq "") && [info exists tcl_interactive] && $tcl_interactive} {

只是检查水平的东西

if ([info level] == 1} {

你应该得到你想要的。

于 2012-01-05T14:01:33.487 回答
0

Vaghan,你确实有正确的解决方案。使用 ::unknown 是 tclsh 本身如何提供您正在谈论的交互式外壳功能(调用外部二进制文件等)。而且您已经提取了相同的代码并将其包含在您的 MyShell 中。

但是,如果我理解你对它是一个“丑陋的解决方案”的担忧,你宁愿不重置 ::unknown 吗?

在这种情况下,为什么不将您想要的附加功能附加到预先存在的 ::unknown 主体的末尾(或添加它 - 您选择)

如果你在 Tcl'ers wiki 上搜索“让未知知道”,你会看到一个简单的过程来证明这一点。它将新代码添加到现有的 ::unknown 之前,因此您可以继续添加额外的“后备代码”。

(如果我误解了你为什么觉得你的解决方案“丑陋”,我深表歉意)

于 2013-04-26T11:27:17.490 回答
0

我建议您进行更改以评估表达式,而不是更改unknownproc

if {([info level] == 1) && ([info script] eq "") && [info exists tcl_interactive] && $tcl_interactive} {

为真。

  • info level:调用你的东西uplevel #0 $code
  • info script:调用info script {}将其设置为空值
  • tcl_interactive. 简单的:set ::tcl_interactive 1

所以你的代码是

proc prompt { } {
    puts -nonewline stdout "MyShell > "
    flush stdout
}

proc process { } {
    catch { uplevel #0 [gets stdin] } got
    if { $got ne "" } {
        puts stderr $got
        flush stderr
    }
    prompt
}

fileevent stdin readable process
set tcl_interactive 1
info script {}
prompt
vwait forever
于 2013-04-27T11:21:06.377 回答