Tracing a script's execution

Arjen Markus I read a question the other day on c.l.t. about tracing a script and I replied to a reply from Larry Virden, but while I misunderstood the question and his answer, I kept thinking "what does ksh -x actually do?" Then the light went on and I realised what I should have done, come up with a small script like the one below.

While it is far from perfect (I have one script that seems to get stuck with it), it is a beginning. For the moment: here it is "as is". Maybe I will be able to perfect it later:

  • File names with paths are maltreated
  • Just puts the line is not very ingenious, you could write an interactive procedure for it instead, using something like A minimal debugger, as shown in Steppin' out.

 # trace_script.tcl --
 #    Trace a script that is being executed
 #    Usage:
 #    tclsh trace_script.tcl script-to-run args
 #

 # namespace Trace --
 #    Private namespace
 # 
 namespace eval ::Trace {
    variable traceOut stdout
 }

 # prepareLine --
 #    Prepare the line of code for output
 #
 # Arguments:
 #    line             Line of code 
 # Result:
 #    Line of code with [ and ] escaped
 #
 proc ::Trace::prepareLine {line} { 
    string map {[ \\\[ ] \\\] \" \\\" \{ \\\{ \} \\\} } $line
 }

 # source --
 #    Redefine the source command to manipulate the source
 #    of the script
 #
 # Arguments:
 #    filename         Name of the file to be sourced
 # Result:
 #    Whatever the source file does
 # Side effects:
 #    The manipulated source file is loaded
 #
 if {[info commands ::tcl::source] == {}} then {
   rename source ::tcl::source
 }
 proc source {filename} { 
    set infile  [open $filename "r"]
    set outfile [open TMP_$filename "w"]
    
    while { [gets $infile line] >=0 } {
       puts $outfile "puts \$::Trace::traceOut \"[::Trace::prepareLine $line]\""
       puts $outfile $line
    }
    close $infile
    close $outfile
  
    ::tcl::source TMP_$filename
    file delete -force TMP_$filename
 } 

 # main --
 #    Prepare argv and source the code
 # 
 namespace import 
 catch {
    console show 
 }
 set filename [lindex $argv 0]
 set argv     [lrange $argv 1 end]
 source $filename

MSW says, nice :) I've taken the freedom to change the target of the rename to ::tcl::source and remove the temporary file later.

RS: Writing a temporary file isn't necessary - just collect the "instrumented" code in a string variable and eval that. But I see a bigger danger: not every line contains a single command - sometimes it's less, or more. Consider

 example with a long line that we want to continue \
     on the next line
 set data {
   red
   green
   blue
 }

The putses will be inserted both inside the 'example' command, and in the 'data' block, probably both leading to unexpected behavior. Better use trace execution, it's built in - see Steppin' out