为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
泛舟湖上清波郎朗 2021-12-07 16:41:07
我编写了以下用于执行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}运行时,函数执行没有错误,但输出文件为空。相同的代码适用于其他类似的命令,但不适用于此特定snt2cooc命令,我注意到当我直接在 shell 中运行此命令时:snt2cooc file1.vcb file2.vcb file3.snt我得到以下输出:END.0 20 30 40 50 6(为简洁起见被截断)如果我直接从 shell 将命令的输出发送到一个文件:snt2cooc file1.vcb file2.vcb file3.snt > out.txt内容out.txt如预期:0 20 30 40 50 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) 提供一种非 hacky 的方式(即 500 毫秒睡眠)来解决它。
查看完整描述

2 回答

?
撒科打诨

TA贡献1934条经验 获得超2个赞

首先,清理你的代码。


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

}

跑:


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

此外,当您运行此代码并cmd.Stdout = os.Stdout替换cmd.Stdout = outfile.


查看完整回答
反对 回复 2021-12-07
?
HUWWW

TA贡献1874条经验 获得超12个赞

问题不在于内部,SNTtoCooc而在于您如何使用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) 

        }

按照该意见中os/exec


Stdout 和 Stderr 指定进程的标准输出和错误。如果其中一个为 nil,则 Run 将相应的文件描述符连接到空设备 (os.DevNull)。


查看完整回答
反对 回复 2021-12-07
  • 2 回答
  • 0 关注
  • 217 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信