2012-01-04 133 views
1

tclsh是一個包含TCL命令的shell。如何評估tclsh腳本?

TCL uplevel命令評估給定的TCL腳本,但它無法評估tclsh腳本(它可能包含bash命令)。

如何獲得tclsh腳本的uplevel的模擬?


考慮這個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外殼,所以當你鍵入tclsh main.tcl它顯示一個提示MyShell >,它就像你在互動tclsh會議。但是,您處於非交互式tclsh會話中,並且您輸入的所有內容均由uplevel命令評估。所以在這裏你不能像使用bash命令那樣使用int interactive tclsh會話。例如。您無法從shell中打開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的不同版本可能有所不同。

有沒有解決這些問題的解決方案?

+2

我不明白你的問題:tclsh的*是* TCL,您可以在使用的tclsh uplevel。我錯過了什麼?你的意思是說tclsh腳本可以包含bash命令?在一個* interactive * tclsh會話中,'unknown'命令將會執行一個未定義的命令。 – 2012-01-04 23:27:06

+0

@glennjackman請參閱更新。 – Vahagn 2012-01-05 08:36:16

+1

FWIW,我會使用'永遠等待'而不是那個循環,任何需要週期性執行的事情都會在'after'後安排(重複)。 – 2012-01-05 13:42:41

回答

0

我認爲最簡單的答案就是使用你正在使用的方法;重寫unknown命令。具體來說,就是在這一條線檢查,以確保當前上下文

  • 在腳本無法運行
  • 互動
  • 在頂層

如果更換線:

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

的東西,只是檢查水平

if ([info level] == 1} { 

你應該得到你想要的。

1

uplevel不僅評估一個腳本,而且在它執行的實例的調用者的棧上下文中評估它。當你定義自己的執行控制結構時應該使用這個相當先進的命令,而OFC則是TCL特定的 - 我發現自己無法想象應該如何使用tclsh等價物。

如果你只是想評估另一個腳本,正確的TCL命令將是eval。如果其他腳本是tclsh,爲什麼不打開另一個tclsh?

+0

eval的tclsh類似物也很好。 – Vahagn 2012-01-04 21:42:37

+0

我不能使用另一個tclsh會話的原因是我必須在TCL腳本中編寫一種tclsh。 – Vahagn 2012-01-04 21:45:21

0

Vaghan,你有正確的解決方案。 Using :: unknown是tclsh本身如何提供你正在談論的交互式shell功能(調用外部二進制文件等)。你已經提取了相同的代碼並將其包含在你的MyShell中。但是,如果我理解你對它是一個「醜陋的解決方案」的擔憂,你寧願不重置::未知?

在這種情況下,爲什麼不只是追加要預先存在的未知::身體末端的附加功能(或預先準備它 - 你選擇)

如果您在Tcl'ers維基搜索對於「讓未知的人知道」,你會看到一個簡單的過程來證明這一點。它將新代碼添加到現有的:: unknown,因此您可以繼續添加額外的「後備代碼」。

(道歉,如果我誤解你爲什麼覺得你的解決方案是「醜」)

0

而不是改變unknown PROC,我建議你進行更改,以評估expresion

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