使用<(..)参数执行

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

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
"))
    if err != nil {
        log.Fatal(err)
    }
    defer foo.Close()

    bar, err := readerFile(strings.NewReader("bar
"))
    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).

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.