2012-10-26 47 views
4

我有一個tcl腳本,它串行運行多個shell命令。從TCL腳本和格式化輸出在後臺運行命令

事情是這樣的:

abc.tcl

command 1 
command 2 
command 3 
... 
command n 

此腳本會打印這些命令的輸出到以下格式的文本文件:

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

Command Output 

### ### ### ### ### ## 

我試圖讓腳本運行得更快,但讓shell命令並行運行而不是串行運行。將它們推到背景(命令a &)。但是我不知道如何保留我的輸出文本文件的格式,就像以前一樣。

當我在背景推命令我被迫到其輸出追加到一個臨時文件,但這些文件只是命令的輸出轉儲到一起。很難區分不同的產出。

有什麼方法我也可以把在後臺運行一個單獨的臨時文件的每個命令的輸出(也許是臨時文件的名稱可以有後臺運行的進程的進程ID)。一旦所有命令都運行完畢,我可以將輸出一起捕獲到正確的格式中?任何想法/建議,我可以如何完成這一點。

+0

作爲替代方案,也許我可以運行shell命令在後臺將它們推送到各個臨時文件,並在完成執行後收到通知,然後將它們捕獲到主文件中。 我很擔心在同一時間情況下將多個進程寫入同一個文件,不希望輸出文件損壞。 並不真正知道如何表示後臺進程是從tcl腳本內運行完成的。 – egorulz

+0

你可以用'fileevent'完成它;讀取器管道在不再有遠端時變得可讀。 (Writer block ...) –

回答

2

如果命令沒有依賴於對方的狀態,您可以並行他們。有很多方法可以做到這一點,但一個更容易的使用線程包的thread pooling(這需要一個螺紋TCL,在許多平臺上爲標準現在):

package require Thread 

set pool [tpool::create -maxworkers 4] 
# The list of *scripts* to evaluate 
set tasks { 
    {command 1} 
    {command 2} 
    ... 
    {command n} 
} 

# Post the work items (scripts to run) 
foreach task $tasks { 
    lappend jobs [tpool::post $pool $task] 
} 

# Wait for all the jobs to finish 
for {set running $jobs} {[llength $running]} {} { 
    tpool::wait $pool $running running 
} 

# Get the results; you might want a different way to print the results... 
foreach task $tasks job $jobs { 
    set jobResult [tpool::get $pool $job] 
    puts "TASK: $task" 
    puts "RESULT: $jobResult" 
} 

主要tweakable是的大小線程池,默認爲4.(通過-maxworkers選項tpool::create我已經明確上面列出的設置它。)的限制選擇最佳的價值取決於有多少CPU內核你有,有多少CPU負載每任務平均產生;你需要衡量和調整...

您還可以使用-initcmd可選擇預加載池中的每個工作線程與您所選擇的腳本。這是一個放置您的package 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)處理。這些是子過程;他們彼此不分享任何東西,所以您希望他們做的或者配置的任何東西都必須作爲任務的一部分發送。一個更復雜的版本可以保留子進程的熱池,並且一般工作非常像線程池(由於處於子進程而不是線程而產生的細微差異),但是這比我想寫的代碼更多。

結果代碼(即,例外代碼)爲「ok」爲0,「error」爲1,其他值爲不常見情況。它們正是Tcl 8.6 catch manual page中記錄的值;這取決於你是否正確地解釋它們。 (我想我還應該添加代碼以使::errorInfo::errorCode變量的內容在出現錯誤的情況下被報告回來,但這會使代碼更加複雜......)

+0

FWIW,線程包應該隨Tcl 8.6安裝(至少8.6b2)一起發佈,但文檔構建中存在一個漏洞,這意味着線程文檔沒有在www上實時生效.tcl.tk網站。 –

+0

非常感謝您的信息。線程池似乎是這樣做的優雅方式。不幸的是我現在正在運行Tcl 8.4,並且沒有線程包。還有其他方法可以完成類似的事情嗎? – egorulz

+0

@egorulz當然,你可以用子進程來完成。我會花一點時間來研究細節...... –