从第N行执行TCL脚本

 收藏

我有一个TCL脚本,说有30行自动化代码,这些代码我正在dc shell(Synopsys Design编译器)中执行。我要在第10行停止并退出脚本,退出dc shell,并在执行手动检查后再次将其重新备份。但是,这次,我想从第11行开始运行脚本,而不必执行前10行。

我不想使用两个脚本,一个脚本包含直到第10行的代码,而另一个脚本剩下其余的脚本,我想仅使用一个脚本并尝试从第N行开始执行它。

Something like: source a.tcl -line 11

我怎样才能做到这一点?

回复
  • yvero 回复

    If you have Tcl 8.6+ and if you consider re-modelling your script on top of a Tcl coroutine, you can realise this continuation behaviour in a few lines. This assumes that you run the script from an interactive Tcl shell (dc shell?).

    # script.tcl
    if {[info procs allSteps] eq ""} {
        # We are not re-entering (continuing), so start all over.
        proc allSteps {args} {
          yield; # do not run when defining the coroutine;
          puts 1
          puts 2
          puts 3
          yield; # step out, once first sequence of steps (1-10) has been executed
          puts 4
          puts 5
          puts 6
          rename allSteps ""; # self-clean, once the remainder of steps (11-N) have run
        }
        coroutine nextSteps allSteps
    }
    
    nextSteps; # run coroutine
    
    1. Pack your script into a proc body (allSteps).
    2. Within the proc body: Place a yield to indicate the hold/ continuation point after your first steps (e.g., after the 10th step).
    3. Create a coroutine nextSteps based on allSteps.
    4. Protect the proc and coroutine definitions in a way that they do not cause a re-definition (when steps are pending)

    Then, start your interactive shell and run source script.tcl:

    % source script.tcl
    1
    2
    3
    

    现在,执行您的手动检查。然后,从同一外壳中继续:

    % source script.tcl
    4
    5
    6
    

    Note that you can run the overall 2-phased sequence any number of times (because of the self-cleanup of the coroutine proc: rename):

    % source script.tcl
    1
    2
    3
    % source script.tcl
    4
    5
    6
    

    再次:所有这些都假定您没有退出外壳,并在执行检查时维护外壳。如果出于某种原因(或无法运行Tcl 8.6+)需要退出shell,那么Donal的建议就是正确的选择。

    更新资料

    If applicable in your case, you may improve the implementation by using an anonymous (lambda) proc. This simplifies the lifecycle management (avoiding re-definition, managing coroutine and proc, no need for a rename):

    # script.tcl
    if {[info commands nextSteps] eq ""} {
        # We are not re-entering (continuing), so start all over.
        coroutine nextSteps apply {args {
          yield; # do not run when defining the coroutine;
          puts 1
          puts 2
          puts 3
          yield; # step out, once first sequence of steps (1-10) has been executed
          puts 4
          puts 5
          puts 6
        }}
    }
    
    nextSteps
    

  • 依賴你所有 回复

    The simplest way is to open the text file, parse it to get the first N commands (info complete is useful there), and then evaluate those (or the rest of the script). Doing this efficiently produces slightly different code when you're dropping the tail as opposed to when you're dropping the prefix.

    proc ReadAllLines {filename} {
        set f [open $filename]
        set lines {}
        # A little bit careful in case you're working with very large scripts
        while {[gets $f line] >= 0} {
            lappend lines $line
        }
        close $f
        return $lines
    }
    
    proc SourceFirstN {filename n} {
        set lines [ReadAllLines $filename]
        set i 0
        set script {}
        foreach line $lines {
            append script $line "\n"
            if {[info complete $script] && [incr i] >= $n} {
                break              
            }
        }
        info script $filename
        unset lines
        uplevel 1 $script
    }
    
    proc SourceTailN {filename n} {
        set lines [ReadAllLines $filename]
        set i 0
        set script {}
        for {set j 0} {$j < [llength $lines]} {incr j} {
            set line [lindex $lines $j]
            append script $line "\n"
            if {[info complete $script]} {
                if {[incr i] >= $n} {
                    info script $filename
                    set realScript [join [lrange $lines [incr j] end] "\n"]
                    unset lines script
                    return [uplevel 1 $realScript]
                }
                # Dump the prefix we don't need any more
                set script {}
            }
        }
        # If we get here, the script had fewer than n lines so there's nothing to do
    }
    

    Be aware that the kinds of files you're dealing with can get pretty large, and Tcl currently has some hard memory limits. On the other hand, if you can source the file at all, you're already within that limit…