英文:
go exec with <(..) argument
问题
我正在尝试使用exec来获取等效的结果:
diff <(echo "foo") <(echo "bar")
这将产生以下输出:
1c1
< foo
---
> bar
但是当我尝试以下代码时:
cmd := exec.Command("diff", "<(echo foo)", "<(echo bar)")
输出结果为:
exit status 2
diff: <(echo foo): No such file or directory
diff: <(echo bar): No such file or directory
英文:
I'm trying to get the equivalent using exec
diff <(echo "foo") <(echo "bar")
which produces:
1c1
< foo
---
> bar
But when I try:
cmd := exec.Command("diff", "<(echo foo)", "<(echo bar)")
The output is:
exit status 2
diff: <(echo foo): No such file or directory
diff: <(echo bar): No such file or directory
答案1
得分: 4
FUZxxl的答案很简单,但需要使用bash。Bash并不是真正的跨平台,作为解释器容易受到代码注入的影响。我首先会描述一种使用Go复制这个过程的方法,然后再描述git使用的答案。
在bash中,<(echo foo)
有两个作用。首先,它创建一个文件描述符,用于传递给调用的命令。其次,它将/dev/fd/n
作为参数传递。你可以尝试以下命令进行确认:
echo <(echo foo) <(echo bar)
Echo会忽略文件描述符,并将其作为字符串打印出来。
以下Go代码将实现类似的功能:
package main
import (
"io"
"log"
"os"
"os/exec"
"strings"
)
func main() {
cmd := exec.Command("diff", "/dev/fd/3", "/dev/fd/4")
foo, err := readerFile(strings.NewReader("foo\n"))
if err != nil {
log.Fatal(err)
}
defer foo.Close()
bar, err := readerFile(strings.NewReader("bar\n"))
if err != nil {
log.Fatal(err)
}
defer bar.Close()
cmd.ExtraFiles = []*os.File{foo, bar}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
log.Fatal(err)
}
}
func readerFile(r io.Reader) (*os.File, error) {
reader, writer, err := os.Pipe()
if err != nil {
return nil, err
}
go func() {
io.Copy(writer, r)
writer.Close()
}()
return reader, nil
}
显然,这只适用于类Unix系统,因此不太跨平台。但它对代码注入是免疫的。
Git的方法
Git只是创建临时文件,然后进行比较。这种方法非常跨平台且免疫于注入。只需使用ioutil.TempFile来创建这两个文件,然后运行exec.Command("diff", file1, file2)
。
英文:
FUZxxl's answer is simple, but it requires the use of bash. Bash is not really cross platform and as an interpreter is open to code injection. I will first describe a way to replicate this with Go, then describe the answer git uses.
The <(echo foo) does two things in bash. First, it creates a file descriptor to be passed to the command being called. The second thing it does is pass /dev/fd/n as an argument. For confirmation, try:
echo <(echo foo) <(echo bar)
Echo ignores the fds and just prints the file descriptor as a string.
The following Go code will do something similar:
package main
import (
"io"
"log"
"os"
"os/exec"
"strings"
)
func main() {
cmd := exec.Command("diff", "/dev/fd/3", "/dev/fd/4")
foo, err := readerFile(strings.NewReader("foo\n"))
if err != nil {
log.Fatal(err)
}
defer foo.Close()
bar, err := readerFile(strings.NewReader("bar\n"))
if err != nil {
log.Fatal(err)
}
defer bar.Close()
cmd.ExtraFiles = []*os.File{foo, bar}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
log.Fatal(err)
}
}
func readerFile(r io.Reader) (*os.File, error) {
reader, writer, err := os.Pipe()
if err != nil {
return nil, err
}
go func() {
io.Copy(writer, r)
writer.Close()
}()
return reader, nil
}
Obviously, this will only work on Unix like systems and is therefore not very cross platform. It is immune to code injection though.
GIT's way
Git just makes temporary files and then compares them. This is extremely cross platform and immune to injections. Just use ioutil.TempFile to create both the files and then run exec.Command("diff", file1, file2)
.
答案2
得分: 3
<(echo foo)
语法是由 shell(如 sh
或 bash
)解释的。exec.Command
函数不会在提供的参数上调用 shell,它直接执行一个进程。如果你想要调用 shell,那么就调用 shell:
exec.Command("bash", "-c", "diff <(echo foo) <(echo bar)")
在这个例子中,我使用 bash
,因为在许多系统上,sh
不支持 <(cmd)
语法。尽可能使用 sh
来增加应用程序的可移植性。
英文:
The <(echo foo)
syntax is interpreted by the shell, sh
or bash
. The function exec.Command
does not invoke the shell on the arguments you provide, it directly executes a process. If you want to invoke the shell, then invoke the shell:
exec.Command("bash", "-c", "diff <(echo foo) </echo bar)")
In this example I use bash
as on many systems, sh
does not implement the <(cmd)
syntax. Try to use sh
whenever possible to increase the portability of your applications.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论