【问题标题】:How to write the output of an executable binary into memory rather than disk如何将可执行二进制文件的输出写入内存而不是磁盘
【发布时间】:2022-11-08 15:12:50
【问题描述】:

我得到了一个像下面这样工作的二进制文件:

> ./my_bin raw.avi output_file.avi

output_file.avi 是我想要的,作业成功后会在终端打印一些详细信息,例如:

版权所有 2022 Company Inc... 成功。

我想在我的代码中运行这个命令并将output_file.avi 重定向到某个字节数组中,这样我就不必从磁盘读取它并删除它。我的方法看起来像下面的 Golang sn-p:

func wrongOne(stdin []byte) ([]byte, error) {
    inBuf := bytes.NewBuffer(stdin)
    outBuf := bytes.NewBuffer(nil)
    cmd := exec.Command("./my_bin", "/dev/stdin", "/dev/stdout")
    cmd.Stdin = inBuf
    cmd.Stdout = outBuf
    err := cmd.Run()
    if err != nil {
        return nil, err
    }
    return outBuf.Bytes(), nil // wrong
}

但是,返回的字节数组比下面的方法长,导致 MD5 检查失败。

func correctOne(stdin []byte) ([]byte, error) {
    inBuf := bytes.NewBuffer(stdin)
    cmd := exec.Command("./my_bin", "/dev/stdin", "output_file")
    cmd.Stdin = inBuf
    err := cmd.Run()
    if err != nil {
        return nil, err
    }
    return os.ReadFile("output_file")
}

wrongOne 函数可以修改为以下代码才能正确:

func modifiedWrongOne(stdin []byte) ([]byte, error) {
    inBuf := bytes.NewBuffer(stdin)
    outBuf := bytes.NewBuffer(nil)
    cmd := exec.Command("./my_bin", "/dev/stdin", "/dev/stdout")
    cmd.Stdin = inBuf
    cmd.Stdout = outBuf
    err := cmd.Run()
    if err != nil {
        return nil, err
    }
    correct, _ := correctOne(stdin)
    return outBuf.Bytes()[:len(correct)], nil // diff
}

我假设输出详细信息包含在/dev/stdout 中,因此wrongOne 函数不起作用。 IE。,

wrongOne 的输出 = correctOne 的输出 + []byte{"Copyright 2022 Company Inc... Success."}

有什么解决方案可以让我在管道中获取output_file.avi 而无需将其保存为文件并从磁盘读取?谢谢!

【问题讨论】:

  • 快速破解是:output = bytes.TrimSuffix(output, []byte{"Copyright 2022 Company Inc... Sucess"})(可能在某处带有"\n"
  • @LeGEC 遗憾的是,详细信息将包含与输入相关的某些信息,使用静态字符串进行修剪将不起作用,因为它会不时更改:(

标签: linux bash go


【解决方案1】:

该命令将版权声明写入标准输出。为避免将版权声明与输出文件混合,请使用 /dev/stdout 以外的文件作为输出文件。

下面的函数使用Cmd.ExtraFilespipe连接到子进程中的fd 3。该函数将数据从管道复制到字节缓冲区并将这些字节返回给调用者。

func otherOne(stdin []byte) ([]byte, error) {
    r, w, err := os.Pipe()
    if err != nil {
        return nil, err
    }
    defer r.Close()
    defer w.Close()

    cmd := exec.Command("./my_bin", "/dev/stdin", "/dev/fd/3")
    cmd.Stdin = bytes.NewReader(stdin)
    cmd.ExtraFiles = []*os.File{w} // The first file is fd 3.
    if err := cmd.Start(); err != nil {
        return nil, err
    }
    w.Close()
    var outbuf bytes.Buffer
    if _, err := io.Copy(&outbuf, r); err != nil {
        return nil, err
    }
    if err := cmd.Wait(); err != nil {
        return nil, err
    }
    return outbuf.Bytes(), nil
}

【讨论】:

  • 感谢您的详细回答。这是另一个问题:这个函数是线程安全的吗?我们可能有多个请求同时调用这个程序。他们在写/读/dev/fd/3/dev/stdin 时会不会有冲突?
  • @WillZhao:exec.Command(...) 将创造一个全新的过程, /dev/stdin/dev/fd/3 是为每个进程定义的。所以是的:你可以同时运行这些。
【解决方案2】:

几个月后,我想出了另一种解决这个问题的方法。基本思想与Cerise类似,即使用/dev/fd/3重定向输出。这是解决方案。

#gen.sh
./mybin /dev/stdin /dev/fd/3 3>&1 1>&2
// gen.go
func gen(stdin []byte) ([]byte, error) {
    inBuf := bytes.NewBuffer(stdin)
    outBuf := bytes.NewBuffer(nil)
    cmd := exec.Command("./gen.sh")
    cmd.Stdin = inBuf
    cmd.Stdout = outBuf
    cmd.Stderr = os.Stderr
    err := cmd.Run()
    if err != nil {
        return nil, err
    }
    return outBuf.Bytes(), nil
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-16
    • 2020-07-15
    • 1970-01-01
    • 2010-12-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-29
    相关资源
    最近更新 更多