Sometimes I just want to step quickly through a new script without the overhead and learning curve of a full GUI IDE. So I wrote this command-line quickie debugger. Use '''::stepsource::StepSource ''' instead of '''source ''' and the file contents will be instrumented and run line-by-line (advance by hitting carriage return). Breakpoints, advancing to a line number and advancing to end of current procedure are also supported. Here's the minimal help text: run until line number run next line a list array values b run until next breakpoint b ? list breakpoints b set breakpoint b - unset breakpoint b - unset all breakpoints c list changed variable values e run to end of current procedure g list global variables h help l list all instrumented lines l [] list line numbers v list variable values x abort execution execute as tcl command At each step a list of the local variables will be dumped to the console, followed by the next line to be executed. Note variable values changed since the last step are denoted with a -> symbol. At the prompt hit carriage return or one of the commands above. Using 'c' toggles the display so that only changed variables are displayed at each step. Use 'v' to toggle back to default. After the script is "sourced" the instrumented procedures remain, and can be interacted with individually by calling them on the command line as you would ordinarily, or use '''::ss '''' ''' to force stepping variable resets. Sourcing multiple files in one session is supported. In theory (though I haven't tried it) you could replace ''source'' with ''StepSource'' and debug an entire project, or debug a script from within a starkit. Input and output channel can be re-targeted from stdin and stdout to any valid file or socket id, so you could debug a server or embedded app remotely as it runs ''in situ'' (again, not tried by me). namespace eval ::stepsource { proc StepCommand {stepcommand} { switch -regexp -- $stepcommand { ^\[0-9\]+$ { set ::stepsource::currentBreakPoint $stepcommand return } ^b\ *-*[0-9?]*$ { if {$stepcommand == "b -"} {set ::stepsource::breakPoints {} ; return} set bOption [lindex $stepcommand 1] if {$bOption == "?"} {puts $::stepsource::outChannel "breakpoints: $::stepsource::breakPoints" ; return} if {![string first - $bOption]} { set eraseBreakPoint [lsearch $::stepsource::breakPoints [expr abs($bOption)]] if {$eraseBreakPoint > -1} { set ::stepsource::breakPoints [lreplace $::stepsource::breakPoints $eraseBreakPoint $eraseBreakPoint] } puts $::stepsource::outChannel "breakpoints: $::stepsource::breakPoints" return } set ::stepsource::breakPoints "$::stepsource::breakPoints $bOption" set ::stepsource::breakPoints [lsort -unique $::stepsource::breakPoints] if {$bOption == {}} {set ::stepsource::currentBreakPoint $bOption} return } ^l\ *[0-9]*\ *[0-9]*$ { regexp {l ([0-9]+) *([0-9]*)} $stepcommand trash listStart listEnd if ![info exists listStart] { set listStart 1 set listEnd $::stepsource::lineCount } else { if {(![string is integer -strict $listEnd]) || ($listEnd < $listStart)} {set listEnd $listStart} } for {set i $listStart} {$i <= $listEnd} {incr i} { if ![info exists ::stepsource::lineArray($i)] {return} puts -nonewline $::stepsource::outChannel "$::stepsource::lineArray($i)" } return } } switch -- $stepcommand { a { foreach var [uplevel 3 info vars] { if ![uplevel 3 array exists [list $var]] {continue} puts $::stepsource::outChannel "-----------------------------------" uplevel 3 parray [list $var] } } c { foreach var [lsort [uplevel 3 info vars]] { if {$var == "errorInfo"} {continue} if ![uplevel 3 info exists [list $var]] {continue} if [uplevel 3 array exists [list $var]] {continue} set changeIcon "==" catch {if {$::stepsource::varValues($var) != [uplevel 3 set [list $var]]} {set changeIcon "->"}} if {![info exists ::stepsource::varValues($var)]} {set changeIcon "->"} if {$changeIcon == "->"} {puts $::stepsource::outChannel [format "%-30s %s %s" $var $changeIcon "[uplevel 3 set [list $var]]"]} set varrayadd $var ; lappend varrayadd [uplevel 3 set [list $var]] ; array set ::stepsource::currentValues $varrayadd } set ::stepsource::varDefault c } e { set level [expr [info level] - 1] set ::stepsource::watchLevel $level if {$level <= $::stepsource::highestLevel} {unset ::stepsource::watchLevel} } g { foreach var [lsort [info globals]] { if [array exists ::$var] {puts $::stepsource::outChannel [format "%-27s %s" $var Array:] ; continue} set changeIcon "==" catch {if {$::stepsource::varValues($var) != [set $var]} {set changeIcon "->"}} puts $::stepsource::outChannel [format "%-30s %s %s" $var $changeIcon "[set ::$var]"] set varrayadd $var ; lappend varrayadd [set ::$var] ; array set ::stepsource::currentValues $varrayadd } } h { puts $::stepsource::outChannel {\ run until line number run next line a list array values b run until next breakpoint b ? list breakpoints b set breakpoint b - unset breakpoint b - unset all breakpoints c list changed variable values e run to end of current procedure g list global variables h help l list all instrumented lines l [] list line numbers v list variable values x abort execution execute as tcl command } } v { foreach var [lsort [uplevel 3 info vars]] { if ![uplevel 3 info exists [list $var]] {continue} if [uplevel 3 array exists [list $var]] {puts $::stepsource::outChannel [format "%-27s %s" $var Array:] ; continue} set changeIcon "==" catch {if {$::stepsource::varValues($var) != [uplevel 3 set [list $var]]} {set changeIcon "->"}} if {![info exists ::stepsource::varValues($var)]} {set changeIcon "->"} puts $::stepsource::outChannel [format "%-30s %s %s" $var $changeIcon "[uplevel 3 set [list $var]]"] set varrayadd $var ; lappend varrayadd [uplevel 3 set [list $var]] ; array set ::stepsource::currentValues $varrayadd } set ::stepsource::varDefault v } x { error "abort" } {} { set ::stepsource::currentBreakPoint 0 } default { catch {uplevel 3 $stepcommand} result puts $::stepsource::outChannel $result } } } proc StepNumber {linenumber} { set level [info level] if ![info exists ::stepsource::highestLevel] {set ::stepsource::highestLevel $level} if {$level < $::stepsource::highestLevel} {set $::stepsource::highestLevel $level} if ![info exists ::stepsource::currentBreakPoint] {set ::stepsource::currentBreakPoint 0} if {$::stepsource::currentBreakPoint > $::stepsource::lineCount} {set ::stepsource::currentBreakPoint $::stepsource::lineCount} set returnOK 1 catch { if {[info level] < $::stepsource::watchLevel} { unset ::stepsource::watchLevel set returnOK 0 } else { set ::stepsource::currentBreakPoint {} } } if {$::stepsource::currentBreakPoint == 0} {set returnOK 0} if {$linenumber == $::stepsource::currentBreakPoint} {unset ::stepsource::currentBreakPoint ; set returnOK 0} if {[lsearch -exact $::stepsource::breakPoints $linenumber] > -1} {set returnOK 0} if $returnOK {return} catch { set currentProcedure [lindex [info level -2] 0] if {[uplevel 2 info procs $currentProcedure] == {}} {set currentProcedure {}} } if ![info exists ::stepsource::lastProcedure] {set ::stepsource::lastProcedure {}} if ![info exists currentProcedure] {set currentProcedure {}} if {($level != $::stepsource::highestLevel) && ($::stepsource::lastProcedure != $currentProcedure)} {puts $::stepsource::outChannel "||||current procedure: $currentProcedure"} set ::stepsource::lastProcedure $currentProcedure set stepCommand $::stepsource::varDefault StepCommand $stepCommand while {$stepCommand != {}} { puts $::stepsource::outChannel "\n-----------------------------------" puts $::stepsource::outChannel $::stepsource::lineArray($linenumber)\n puts -nonewline $::stepsource::outChannel > set stepCommand [gets $::stepsource::inChannel] StepCommand $stepCommand if {([string is integer -strict $stepCommand]) || ($stepCommand == "b") || ($stepCommand == {}) || ($stepCommand == "e")} { catch {array set ::stepsource::varValues [array get ::stepsource::currentValues]} catch {array unset ::stepsource::currentValues} break } } } proc StepSource {filename} { namespace eval ::stepsource {} set ::stepsource::filename $filename namespace eval ::stepsource { if {[info procs original_unknown] == {}} { rename ::unknown original_unknown proc ::unknown {args} { if [string is integer -strict $args] { ::stepsource::StepNumber $args } else { set ::stepsource::unk_args $args uplevel 1 ::stepsource::original_unknown $::stepsource::unk_args } } } if ![info exists inChannel] {set inChannel stdin} if ![info exists outChannel] {set outChannel stdout} if ![info exists breakPoints] {set breakPoints {}} if ![info exists varDefault] {set varDefault v} if ![info exists sourcedFiles] {set sourcedFiles {}} if {[lsearch -exact $sourcedFiles $filename] < 0} {lappend sourcedFiles $filename} if ![info exists ::stepsource::sourceProcs] {set ::stepsource::sourceProcs {}} set mtime [file mtime $filename] set oldMtime 0 catch {set oldMtime $mtimes($filename)} array unset lineArray set lineCount 1 foreach sF $sourcedFiles { set $sF {} set f [open $sF r] set noNumberLine {} while {![eof $f]} { set line [gets $f] set firstWord [string trim [string range [string trim $line] 0 [expr [string wordend [string trim $line] 0] - 1]]] set secondWord [string trim [string range [string trim $line] [string length $firstWord] [string wordend [string trim $line] [expr [string length $firstWord] + 1]]]] if ![regexp {(::[^ ]+)(\ |$)} $line trash firstNameSpace] {set firstNameSpace {}} if {$firstWord == ":"} {set firstWord $firstNameSpace} if {[string index $secondWord 0] == ":"} {set secondWord $firstNameSpace} if {$firstWord == "proc"} {lappend ::stepsource::sourceProcs $secondWord} if {([info commands $firstWord] != {}) || ([lsearch -exact $::stepsource::sourceProcs $firstWord] > -1)} { set $sF "[set $sF]$noNumberLine[set lineCount]\;\t$line\n" set arrayadd $lineCount ; lappend arrayadd $noNumberLine$lineCount\;\t$line\n ; array set lineArray $arrayadd set noNumberLine {} incr lineCount } elseif {($firstWord == "\{") && (([info commands $secondWord] != {}) || ([lsearch -exact $::stepsource::sourceProcs $secondWord] > -1))} { set arrayadd $lineCount ; lappend arrayadd $noNumberLine$lineCount\;\t$line\n ; array set lineArray $arrayadd regsub {\{} $line "\{$lineCount\;" line set $sF "[set $sF]$noNumberLine\t$line\n" set noNumberLine {} incr lineCount } else { set noNumberLine $noNumberLine\t$line\n } } close $f if {$noNumberLine != {}} {set $sF "[set $sF]$noNumberLine"} } } set ::stepsource::sourceProcs {} uplevel 1 eval \$\{::stepsource::$::stepsource::filename\} } } # end namespace eval ::stepsource proc ::ss {args} { catch {unset ::stepsource::watchLevel} catch {unset ::stepsource::currentBreakPoint} catch {array unset ::stepsource::varValues} uplevel 1 $args }