英文:
How to wrap exec.Command inside an io.Writer
问题
我正在尝试使用mozjpeg在Go语言中压缩JPEG图像。由于它没有官方的Go绑定,我打算调用其命令行界面来进行压缩。
我试图模仿compress/gzip
的用法:
c := jpeg.NewCompresser(destFile)
_, err := io.Copy(c, srcFile)
现在的问题是,我该如何将命令行界面包装在Compresser中,以支持这种用法?
我尝试了以下代码:
type Compresser struct {
cmd exec.Command
}
func NewCompressor(w io.Writer) *Compresser {
cmd := exec.Command("jpegtran", "-copy", "none")
cmd.Stdout = w
c := &Compresser{cmd}
return c
}
func (c *Compresser) Write(p []byte) (n int, err error) {
if c.cmd.Process == nil {
err = c.cmd.Start()
if err != nil {
return
}
}
// 我该如何将p写入c.cmd.Stdin?
}
但是我无法完成它。
另外,第二个问题是,我应该在什么时候关闭这个命令?如何关闭这个命令?
英文:
I'm trying to compress a JPEG image in go using mozjpeg. Since it doesn't have official go binding, I think I'll just invoke its CLI to do the compression.
I try to model the usage after compress/gzip
:
c := jpeg.NewCompresser(destFile)
_, err := io.Copy(c, srcFile)
Now the question is, how do I wrap the CLI inside Compresser so it can support this usage?
I tried something like this:
type Compresser struct {
cmd exec.Command
}
func NewCompressor(w io.Writer) *Compresser {
cmd := exec.Command("jpegtran", "-copy", "none")
cmd.Stdout = w
c := &Compresser{cmd}
return c
}
func (c *Compresser) Write(p []byte) (n int, err error) {
if c.cmd.Process == nil {
err = c.cmd.Start()
if err != nil {
return
}
}
// How do I write p into c.cmd.Stdin?
}
But couldn't finish it.
Also, a second question is, when do I shut down the command? How to shut down the command?
答案1
得分: 1
你应该看一下Cmd.StdinPipe。文档中有一个适合你情况的示例:
package main
import (
"fmt"
"io"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("cat")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
go func() {
defer stdin.Close()
io.WriteString(stdin, "values written to stdin are passed to cmd's standard input")
}()
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", out)
}
在这个例子中,CombinedOutput()
执行你的命令,并且当从 out
中没有更多的字节可读时,执行就完成了。
英文:
You should take a look at the Cmd.StdinPipe. There is an example in the documentation, which suits your case:
package main
import (
"fmt"
"io"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("cat")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
go func() {
defer stdin.Close()
io.WriteString(stdin, "values written to stdin are passed to cmd's standard input")
}()
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", out)
}
In this case, CombinedOutput()
executes your command, and the execution is finished, when there are no more bytes to read from out
.
答案2
得分: 0
根据Kiril的回答,使用cmd.StdInPipe
将接收到的数据传递给Write
方法。
然而,在关闭方面,我倾向于实现io.Closer接口。这将使*Compresser
自动实现io.WriteCloser接口。
我会使用Close()
方法来通知没有更多数据需要发送,并且应该终止命令。如果命令返回非零的退出代码表示失败,可以捕获并作为错误返回。
在Write()
方法中使用CombinedOutput()
时要小心,以防输入流较慢。工具可能已经完成处理输入流,并正在等待更多数据。这将被错误地检测为命令完成,并导致无效的输出。
请记住,在IO操作期间,Write
方法可以被调用多次,次数不确定。
英文:
As per Kiril's answer, use the cmd.StdInPipe
to pass on the data you receive to Write
.
However, in terms of closing, I'd be tempted to implement io.Closer. This would make *Compresser
automatically implement the io.WriteCloser interface.
I would use Close()
as the notification that there is no more data to be sent and that the command should be terminated. Any non-zero exit code returned from the command that indicates failure could be caught and returned as an error.
I would be wary of using CombinedOutput()
inside Write()
in case you have a slow input stream. The utility could finish processing the input stream and be waiting for more data. This would be incorrectly detected as command completion and would result in an invalid output.
Remember, the Write
method can be called an indeterminate number of times during IO operations.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论