2016-01-29 121 views
3

我寫了下面的函數用於執行snt2cooc命令(的預處理步驟之一運行GIZA++。對於我們而言,我認爲我們可以考慮snt2cooc腳本是一個黑色的盒):未能編寫shell命令的輸出到文件中去

func SNTToCOOC(srcVocab, tgtVocab, sntPath, outpath string) error { 
    // open the out file for writing 
    outfile, err := os.Create(outpath) 
    if err != nil { 
     return err 
    } 
    defer outfile.Close() 

    cmdStr := "snt2cooc" 
    args := []string{srcVocab, tgtVocab, sntPath} 
    cmd := exec.Command(cmdStr, args...) 
    cmd.Stdout = outfile 
    if err = cmd.Run(); err != nil { 
     return err 
    } 
    cmd.Wait() 
    return err 
} 

運行時,該函數執行時沒有錯誤,但輸出文件爲空。此相同的代碼適用於其他類似的命令,但不適合這個特定snt2cooc命令,我注意到,當我直接在shell中運行以下命令:

snt2cooc file1.vcb file2.vcb file3.snt

我得到下面的輸出:

END. 
0 2 
0 3 
0 4 
0 5 
0 6 

(截斷簡潔)

如果我直接在命令的輸出發送到一個文件從外殼:

snt2cooc file1.vcb file2.vcb file3.snt > out.txt 

out.txt內容是如預期:

0 2 
0 3 
0 4 
0 5 
0 6 

注意如何在第一種情況下,線END.首先輸出到stdout,才把是發送到stdout命令的實際輸出。因此,我認爲存在一個競爭條件,Go代碼在命令的最終輸出寫入文件之前完成執行。這儘管呼籲cmd.Wait()。我不太確定snt2cooc命令在內部執行什麼操作。有人可以提供一個關於如何解決這個問題的提示嗎?

編輯1:

好像下面的代碼,以500毫秒包含的睡眠,持續寫入輸出到文件的snt2cooc命令:

cmdStr := "snt2cooc" 
args := []string{srcVocab, tgtVocab, sntPath} 
cmd := exec.Command(cmdStr, args...) 
stdout, err := cmd.StdoutPipe() 
time.Sleep(500 * time.Millisecond) 
if err != nil { 
    return err 
} 
err = cmd.Start() 
if err != nil { 
    return err 
} 

out := bufio.NewScanner(stdout) 
for out.Scan() { 
    outfile.Write(out.Bytes()) 
    outfile.WriteString("\n") 
} 
if err := out.Err(); err != nil { 
    return err 
} 

這證明,我認爲有一些競爭狀態正在進行,Go程序在所有輸出寫入文件之前退出。我爲這個問題增加了一個賞金,希望有人能夠1)解釋爲什麼會發生這種情況,並且2)提供一種非破解的方式(即500ms睡眠)來修復它。

+0

您是否在運行後關閉文件之前嘗試調用['Sync'](https://golang.org/pkg/os/#File.Sync)? –

+0

@ Ainar-G我現在試過了,它似乎沒有效果。 –

+0

而'Sync'也不會返回任何錯誤?如果將'bytes.Buffer'設置爲stdout,然後在運行後打印緩衝區的內容,它是否也是空的? –

回答

5

首先,清理你的代碼。

cmd.Stderr = os.DevNull,所以你忽略stderr。 Stdout和Stderr指定進程的標準輸出和錯誤。如果其中任一個爲零,則運行將相應的文件描述符連接到空設備(os.DevNull)。

cmd.Wait()返回error,您忽略它。 func (c *Cmd) Wait() error

Wait等待命令退出。它一定是由Start.啓動的你使用Run,不是Start.

當你運行這段代碼時你得到了什麼輸出?

failure.go

package main 

import (
    "fmt" 
    "os" 
    "os/exec" 
) 

func main() { 
    err := SNTToCOOC("file1.vcb", "file2.vcb", "file3.snt", "out.txt") 
    if err != nil { 
     fmt.Println(err) 
    } 
} 

func SNTToCOOC(srcVocab, tgtVocab, sntPath, outpath string) error { 
    outfile, err := os.Create(outpath) 
    if err != nil { 
     return err 
    } 
    defer outfile.Close() 
    cmdStr := "snt2cooc" 
    args := []string{srcVocab, tgtVocab, sntPath} 
    cmd := exec.Command(cmdStr, args...) 
    cmd.Stdout = outfile 
    cmd.Stderr = os.Stderr 
    err = cmd.Run() 
    if err != nil { 
     return err 
    } 
    return err 
} 

運行:

$ rm -f out.txt && go run failure.go && cat out.txt 

另外,不要當你與cmd.Stdout = os.Stdout運行此代碼代替cmd.Stdout = outfile你所得到的輸出。

+2

謝謝,這讓我發現了一個真正的問題,它出現在一個名爲「SNTToCooc」之前的函數中 - file3.snt' - 我使用'cmd.Start()'而不是'cmd.Run()',所以在調用snt2cooc'之前沒有完成,所以'snt2cooc'正在處理一個空輸入文件。當然,在我從shell中檢查的時候,所有的文件(除了snt2cooc的)都有內容,所以這就是我沒有注意到的原因。我很抱歉把這個問題的範圍太窄,以至於無法發現,但感謝您的幫助! –

-1

的問題是不是與SNTtoCooc的innard但你使用cmd.Stdout如何寫入文件本身:

func anyWrite(args []string, outpath string) error { 
     outfile, err := os.Create(outpath) 
     if err != nil { 
       return err 
     } 

     defer outfile.Close() 

     // I use simple "echo" here 
     cmd := exec.Command("echo", args...) 
     stdout, err := cmd.Output() 
     if err != nil { 
       return err 
     } 

     // Use this instead of cmd.Stdout seems to solve the problem 
     outfile.Write(stdout) 

     return nil 
} 

func main() { 
     args := []string{"Line 1", "Line 2", "Line 3"} 
     if err := anyWrite(args, "./outfile.txt"); err != nil { 
       panic(err) 
     } 
} 

由於每commentsos/exec

輸出和錯誤指定進程的標準輸出和錯誤。如果 或者爲零,則Run將相應的文件描述符連接到空設備(os.DevNull)。

+0

如果我按原樣運行示例,它會報告'exec:already started'('cmd.Output()'運行該命令,不需要第二個'cmd.Run()')。即使如此,在刪除'cmd.Run()'部分之後,使用'echo'命令將參數寫入文件,但是使用'snt2cooc'命令在文件中沒有輸出。所以在我看來,它確實有'snt2cooc'命令本身。 –

+0

你是正確的感謝糾正。在寫入文件之前,您是否嘗試過使用'cmd.Start()'和'cmd.Wait()'? – PieOhPah

+0

是的,我只是嘗試使用'cmd.Start()',然後'cmd.Wait()',但沒有運氣。看起來'snt2cooc'正在寫入標準輸出到與主線程不同的線程中,也許呢? –