我需要在多个主机上并行运行脚本,但是一旦在一个帐户上完成脚本,它就需要移至下一个帐户,而不是等待并行主机在其帐户上完成。我下面的代码对此适用。
所以我遇到的问题是,我需要能够在每个帐户完成后记录在每个帐户上运行的命令的输出。因此,在下面的示例中,我需要每个运行时的回显“ 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版。
答案 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
打包非常容易,并且您不必担心阻塞读取或缓冲,因为包装子进程总是一次写入所有内容,并且(冒号和)之后立即退出。