无法在Go中将shell命令的输出写入文件

Her*_*aaf 3 shell exec go

我编写了以下函数来执行snt2cooc命令(运行GIZA ++的预处理步骤之一.出于我们的目的,我认为我们可以认为snt2cooc脚本是一个黑盒子):

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
}
Run Code Online (Sandbox Code Playgroud)

运行时,该函数执行时没有错误,但输出文件为空.这个代码适用于其他类似命令,但不适用于此特定snt2cooc命令,我注意到当我在shell中直接运行此命令时:

snt2cooc file1.vcb file2.vcb file3.snt

我得到以下输出:

END.
0 2
0 3
0 4
0 5
0 6
Run Code Online (Sandbox Code Playgroud)

(为简洁而截断)

如果我将命令的输出直接从shell发送到文件:

snt2cooc file1.vcb file2.vcb file3.snt > out.txt
Run Code Online (Sandbox Code Playgroud)

内容out.txt如预期:

0 2
0 3
0 4
0 5
0 6
Run Code Online (Sandbox Code Playgroud)

请注意,在第一种情况下,行END.首先输出到stdout,然后才发送到stdout的命令的实际输出.因此,我认为存在竞争条件,Go代码在命令的最终输出写入文件之前完成执行.尽管如此cmd.Wait().我不太清楚这个snt2cooc命令究竟在内部做了什么.有人可以提供一个如何解决这个问题的提示吗?

编辑1:

看起来像下面的代码,包含500ms的睡眠,始终将输出写入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
}
Run Code Online (Sandbox Code Playgroud)

这向我证明存在一些竞争条件,在所有输出写入文件之前,Go程序退出.我为这个问题添加了一笔赏金,希望有人可以1)解释为什么会这样,2)提供一种非黑客方式(即500毫秒睡眠)来修复它.

pet*_*rSO 5

首先,清理你的代码.

cmd.Stderr = os.DevNull,所以你忽略了stderr.Stdout和Stderr指定进程的标准输出和错误.如果其中一个为nil,则Run将相应的文件描述符连接到空设备(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
}
Run Code Online (Sandbox Code Playgroud)

跑:

$ rm -f out.txt && go run failure.go && cat out.txt
Run Code Online (Sandbox Code Playgroud)

此外,当您使用cmd.Stdout = os.Stdout替换运行此代码时,您获得什么输出cmd.Stdout = outfile.

  • 谢谢,这让我发现了真正的问题,这是在`SNTToCooc`之前调用的函数 - 生成`file3.snt`的函数 - 我在那里使用`cmd.Start()`而不是`cmd.运行()`,所以在调用`snt2cooc`之前没有完成,所以`snt2cooc`正在使用空输入文件.当然,当我从shell检查时,所有文件(snt2cooc除外)都有内容,所以这就是我没注意到的原因.对于过于狭隘地构建问题我很抱歉能够发现这一点,但感谢您的帮助! (2认同)