Shell 从TCL脚本在后台运行命令并格式化输出

Shell 从TCL脚本在后台运行命令并格式化输出,shell,redirect,background,tcl,Shell,Redirect,Background,Tcl,我有一个tcl脚本,它可以连续运行多个shell命令 大概是这样的: abc.tcl command 1 command 2 command 3 ... command n 此脚本以以下格式将这些命令的输出打印到文本文件中: ### ### ### ### ### ### Command name ### ### ### ### ### ### Command Output ### ### ### ### ### ## 我试图让脚本运行得更快,但让shell命令并行运行,而不是串行运行

我有一个tcl脚本,它可以连续运行多个shell命令

大概是这样的:

abc.tcl

command 1
command 2 
command 3
...
command n
此脚本以以下格式将这些命令的输出打印到文本文件中:

### ### ### ### ### ###
Command name
### ### ### ### ### ###

Command Output

### ### ### ### ### ##
我试图让脚本运行得更快,但让shell命令并行运行,而不是串行运行。通过将它们推到后台(命令a&)。但我不知道如何像以前一样保留输出文本文件的格式

当我把命令推到后台时,我被迫将它们的输出附加到一个临时文件中,但这些文件只是将命令的输出放在一个转储中。很难区分不同的输出


是否有某种方法可以将在后台运行的每个命令的输出重定向到单个临时文件(可能临时文件的名称可以包含后台运行进程的进程id)。一旦所有命令都运行了,我就可以将输出合并成正确的格式?关于如何实现这一点的任何想法/建议。

如果命令没有相互依赖的状态,您可以将它们并行化。有很多方法可以做到这一点,但其中一种更简单的方法是使用thread包(它需要threaded Tcl,这是当今许多平台上的标准):

主要可调整的是线程池的大小,默认限制为4。(通过
-maxworkers
选项将其设置为
tpool::create
,我已经在上面明确列出了该选项。)选择的最佳值取决于您拥有多少CPU核心以及每个任务平均生成多少CPU负载;你需要测量和调整

您还可以使用
-initcmd
选项使用您选择的脚本预加载池中的每个工作线程。这是一个放置
包require
调用的好地方。所有工人都完全独立于彼此和主线程;他们没有共同的国家。如果您在一个单独的过程中运行每段代码,您将得到相同的模型(但随后您将编写更多代码来进行协调)


[编辑]:这是一个与Tcl 8.4兼容的版本,它使用子流程

namespace eval background {}
proc background::task {script callback} {
    set f [open |[list [info nameofexecutable]] "r+"]
    fconfigure $f -buffering line
    puts $f [list set script $script]
    puts $f {fconfigure stdout -buffering line}
    puts $f {puts [list [catch $script msg] $msg]; exit}
    fileevent $f readable [list background::handle $f $script $callback]
}
proc background::handle {f script callback} {
    foreach {code msg} [read $f] break
    catch {close $f}
    uplevel "#0" $callback [list $script $code $msg]
}

proc accumulate {script code msg} {
    puts "#### COMMANDS\n$script"
    puts "#### CODE\n$code"
    puts "#### RESULT\n$msg"

    # Some simple code to collect the results
    if {[llength [lappend ::accumulator $msg]] == 3} {
        set ::done yes
    }
}
foreach task {
    {after 1000;subst hi1}
    {after 2000;subst hi2}
    {after 3000;subst hi3}
} {
    background::task $task accumulate
}
puts "WAITING FOR TASKS..."
vwait done
注意:任务是生成结果的Tcl命令,但不能打印结果;结构代码(在
background::task
中)处理该问题。这些是子流程;它们彼此不共享任何内容,因此您希望它们执行或配置的任何操作都必须作为任务的一部分发送。一个更复杂的版本可以保留一个热的子进程池,并且在一般情况下工作起来非常像一个线程池(由于处于子进程而不是线程中,可能会有细微的差异),但这比我想在这里编写的代码要多


结果代码(即异常代码)为0表示“正常”,1表示“错误”,在不太常见的情况下为其他值。它们正是记录在图纸上的值;由你来正确解释它们。(我想我还应该添加代码,使
::errorInfo
::errorCode
变量内容在发生错误时报告回来,但这会使代码变得更加复杂…

作为替代方案,也许我可以在后台运行shell命令,并将它们推送到各个临时文件中,一旦他们完成执行,就会得到通知,然后将他们放入主文件。我担心会遇到多个进程同时写入同一个文件的情况,我不希望输出文件损坏。而且我真的不知道如何表明后台进程是在tcl脚本中运行的;当读卡器管道不再具有远端时,读卡器管道变得可读。(编剧阻止…)FWIW,线程包应该随Tcl 8.6安装一起提供(因为至少有8.6b2),但是文档构建中出现了一个错误,这意味着线程文档没有在www.Tcl.tk网站上上线。非常感谢您提供的信息。线程池似乎是一种优雅的方法。不幸的是,我现在运行的是TCL8.4,没有线程包。还有其他方法可以完成类似的任务吗?@egorulz当然,你可以通过子流程来完成。我需要花点时间来研究细节…
namespace eval background {}
proc background::task {script callback} {
    set f [open |[list [info nameofexecutable]] "r+"]
    fconfigure $f -buffering line
    puts $f [list set script $script]
    puts $f {fconfigure stdout -buffering line}
    puts $f {puts [list [catch $script msg] $msg]; exit}
    fileevent $f readable [list background::handle $f $script $callback]
}
proc background::handle {f script callback} {
    foreach {code msg} [read $f] break
    catch {close $f}
    uplevel "#0" $callback [list $script $code $msg]
}

proc accumulate {script code msg} {
    puts "#### COMMANDS\n$script"
    puts "#### CODE\n$code"
    puts "#### RESULT\n$msg"

    # Some simple code to collect the results
    if {[llength [lappend ::accumulator $msg]] == 3} {
        set ::done yes
    }
}
foreach task {
    {after 1000;subst hi1}
    {after 2000;subst hi2}
    {after 3000;subst hi3}
} {
    background::task $task accumulate
}
puts "WAITING FOR TASKS..."
vwait done