TCL获得完成的后台进程的输出

时间:2018-10-25 13:02:27

标签: tcl

我需要在多个主机上并行运行脚本,但是一旦在一个帐户上完成脚本,它就需要移至下一个帐户,而不是等待并行主机在其帐户上完成。我下面的代码对此适用。

所以我遇到的问题是,我需要能够在每个帐户完成后记录在每个帐户上运行的命令的输出。因此,在下面的示例中,我需要每个运行时的回显“ account @ host”输出以及每个运行时产生的退出代码,以确保成功运行每个命令。

我将如何捕获输出?

我的代码如下:

set HOST(host1) "account1 account2 account3 account4"
set HOST(host2) "account1 account3"

# Let make our variables we'll edit and manipulate as we go.
set hostList     [array names HOST]
set activeHosts  ""
set background   ""

set cont 1
while { $cont == 1 } {
    foreach host [array names HOST] {
        if { [lsearch $activeHosts $host] == -1 && $HOST($host) != "" } {

            set script {
puts [exec ssh -A -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null @var1@@@var2@ "echo @var1@@@var2@"]
            }

            set script [string map [list @var1@ [lindex $HOST($host) 0]] $script]
            set script [string map [list @var2@ $host] $script]

            set chan [open |[list [info nameofexecutable] <<$script 2>@stderr]]
            dict set res $chan command $script
            fconfigure $chan -blocking 0

            # Let's keep a backup of what's running in the background
            lappend activeHosts $host
            lappend background  $chan
            set CHANMAP($chan) $host
        } else {
            # Check the processes in the background to see how they are going.
            foreach chan $background {
                if { [eof $chan] } {
                    #############################################
                    # PROCESS HAS ENDED READ THE OUTPUT SOMEHOW #
                    #############################################

                    # We have completed the task on the account / host let's remove it so we don't run on it again
                    set i [lsearch $activeHosts $CHANMAP($chan)]
                    set activeHosts [lreplace $activeHosts $i $i]

                    #Remove the account we've run this on from the list
                    set HOST($CHANMAP($chan)) [lreplace $HOST($CHANMAP($chan)) 0 0]

                    if { $HOST($CHANMAP($chan)) == "" } {
                        # Once we've run out of accounts on the host remove the host
                        set j [lsearch $hostList $CHANMAP($chan)]
                        set hostList [lreplace $hostList $j $j]
                    }

                    fconfigure $chan -blocking 1

                    if { [set idx [lsearch -exact $background $chan]] >= 0 } {
                        set background [lreplace $background $idx $idx]
                    }
                    catch [close $chan] cres copts
                    dict set res $chan result $cres
                    dict set res $chan options $copts
                } else {
                    puts -nonewline [read $chan]
                }
            }
        }
        # If there is nothing left in the hostList then we've run on everything.
        if { [llength $hostList] == 0 } {
            set cont 0
            break
        }
    }

    # Let's wait depending on the task usage so we don't hammer the CPU
    if { $taskLength == "low" } {
        after 100
    } else {
        after 2000
    }
}

我使用的是tcl 8.5版。

1 个答案:

答案 0 :(得分:0)

您想要一个稍微复杂的跑步者脚本。

set script [string map [list @var1@ [lindex $HOST($host) 0] @var2@ $host] {
    set code [catch {
        exec ssh -A -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
                @var1@@@var2@ "echo @var1@@@var2@"
    } msg opt]
    puts [list $code $msg $opt]
    exit
}]

然后您可以启动该脚本,在脚本到达时收集结果,并对其进行处理以发现发生的情况。

set chan [open |[list [info nameofexecutable] <<$script 2>@stderr]]
fconfigure $chan readable [list apply {{chan host} {
    lassign [read $chan] code msg opt
    close $chan
    if {$opt == 0} {
        # Normal exit; $msg has what subsubprocess wrote to stdout
        puts "$host --> $msg"
    } else if {[string match CHILDSTATUS* [dict get $opt -errorcode]]} {
        set exitcode [lindex [dict get $opt -errorcode] 2]
        # Error; exit code now extracted
        puts "$host -($exitcode)-> $msg"
    } else {
        # Something more serious occurred
        puts stderr "PROBLEM($code): $msg $opt"
    }
}} $chan $host]

只要运行事件循环,就可以并行启动几乎任何数量的事件(尽管您可能想停止一百个事件;大多数操作系统未配置为减轻此类负担立刻),Tcl会为您跟踪所有情况。


真正的技巧是将catch两个变量一起使用,然后将这些值打包到整个管道中,然后解压并找出发生了什么情况。用list打包和用lassign打包非常容易,并且您不必担心阻塞读取或缓冲,因为包装子进程总是一次写入所有内容,并且(冒号和)之后立即退出。