在进程完成后读取错误输出(stderr)

3

我使用imagemagick的convert命令处理内存中的一些数据(来自html表单上传/网页服务器)。这很好用,但是如果出现错误,我想获取convert的错误输出。我该怎么做?

这是我的代码:

package main

import (
    "bytes"
    "io"
    "io/ioutil"
    "log"
    "os/exec"
    "path/filepath"
)

func runImagemagick(data []byte, destfilename string) error {
    data_buf := bytes.NewBuffer(data)

    cmd := exec.Command("convert", "-", destfilename)
    stdin, err := cmd.StdinPipe()
    if err != nil {
        return err
    }

    err = cmd.Start()
    if err != nil {
        return err
    }
    _, err = io.Copy(stdin, data_buf)
    if err != nil {
        return err
    }
    stdin.Close()
    err = cmd.Wait()
    if err != nil {
        return err
    }
    return nil
}

func main() {
    data, err := ioutil.ReadFile("source.gif")
    if err != nil {
        log.Fatal(err)
    }
    err = runImagemagick(data, filepath.Join("/tmp", "abc", "dest.png"))
    if err != nil {
        log.Fatal(err)
    }
}

现在的问题是,目录/tmp/abc/不存在。通常convert会给我这个结果:

$ convert - /tmp/abc/foo.png < source.gif 
convert: unable to open image `/tmp/abc/foo.png': No such file or directory @ error/blob.c/OpenBlob/2617.
convert: WriteBlob Failed `/tmp/abc/foo.png' @ error/png.c/MagickPNGErrorHandler/1755.

但是我在我的小程序中没有“看到”这个错误消息。我该如何获取错误消息并向用户显示?

(另一个子问题是:您能否给我建议,如果这段代码看起来可以吗?它有任何明显的缺陷吗?)


你复制数据的方式似乎与Go的理念相矛盾;请参考类似问题的这个答案,了解如何以更符合"Go方式"的方式进行操作。 - kostix
2个回答

1
管道传输标准输出和标准错误。例如,
package main

import (
    "bytes"
    "io"
    "io/ioutil"
    "log"
    "os/exec"
    "path/filepath"
)

func runImagemagick(data []byte, destfilename string) error {
    cmd := exec.Command("convert", "-", destfilename)
    stdin, err := cmd.StdinPipe()
    if err != nil {
        return err
    }
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        return err
    }
    stderr, err := cmd.StderrPipe()
    if err != nil {
        return err
    }
    err = cmd.Start()
    if err != nil {
        return err
    }
    _, err = io.Copy(stdin, bytes.NewBuffer(data))
    if err != nil {
        return err
    }
    stdin.Close()
    outData, err := ioutil.ReadAll(stdout)
    if err != nil {
        return err
    }
    if len(outData) > 0 {
        log.Print(string(outData))
    }
    errData, err := ioutil.ReadAll(stderr)
    if err != nil {
        return err
    }
    if len(errData) > 0 {
        log.Print(string(errData))
    }
    err = cmd.Wait()
    if err != nil {
        return err
    }
    return nil
}

func main() {
    data, err := ioutil.ReadFile("source.gif")
    if err != nil {
        log.Fatal(err)
    }
    err = runImagemagick(data, filepath.Join("/tmp", "abc", "dest.png"))
    if err != nil {
        log.Fatal(err)
    }
}

输出:

2013/03/03 15:02:20 convert.im6: unable to open image `/tmp/abc/dest-0.png': No such file or directory @ error/blob.c/OpenBlob/2638.
convert.im6: WriteBlob Failed `/tmp/abc/dest-0.png' @ error/png.c/MagickPNGErrorHandler/1728.
2013/03/03 15:02:20 exit status 1
exit status 1

1
谢谢!也许这个问题对于评论来说有点太大了,但无论如何:调用 sdout.Close()stderr.Close() 是否必要/良好的实践/...?或者这完全是可选的,因为它们会被其他调用关闭? - topskip
os/exec 包的文档中可以看到,*Cmd.StdoutPipe*Cmd.StdinPipe 方法会自动在 Wait 命令执行后关闭管道。如果你在 Wait 命令之后再使用 ReadAll 读取管道,你将无法读取到任何内容,因为管道已经被关闭了。 - peterSO

1

不需要使用管道,因为bytes.Buffer实现了io.Writer接口,因此可以很好地用于收集程序的输出:

func runImagemagick(data []byte, destfilename string) error {     
    cmd := exec.Command("convert", "-", destfilename)

    var stdout, stderr bytes.Buffer
    cmd.Stdout = &stdout
    cmd.Stderr = &stderr

    err := cmd.Run()
    if err != nil {
        if ee, ok := err.(*exec.ExitError); ok {
            return &imagemagickError{ee, stdout.Bytes(), stderr.Bytes()}
        } else {
            return err
        }
    }

    if stderr.Len() > 0 {
        return errors.New(fmt.Sprintf("imagemagick wrote to stderr: %s", stderr.Bytes()))
    }

    if stdout.Len() > 0 {
        log.Print(stdout.Bytes())
    }
    return nil
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接