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

  • 本文关键字:后台进程 输出 TCL 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版本。

您想要一个稍微复杂一点的runner脚本。

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拆包非常容易,而且您不需要担心阻塞读取或缓冲,因为包装子进程总是一次写入所有内容,然后(刷新和(立即退出。

最新更新