可以在Go中使用类似“ freopen”的构造吗?

In C++ there's a freopen func, which is very useful to r/w files with just stdin/out(cin/cout). So I decided to find similar solution in Go, but found only

import "os"
os.Stdin, err = os.OpenFile("input.txt",
    os.RDONLY | os.O_CREATE, 0666)
os.Stdout, err = os.OpenFile("output.txt",
    os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0666)

, which is not working anymore! Am I wrong? So, do you know other way?

You cannot declare and assign to a variable in another package (os.Stdin, for example).

However this works:

package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    stdin, err := os.OpenFile("input.txt",
        os.O_RDONLY|os.O_CREATE, 0666)
    if err != nil {
        log.Fatal(err)
    }
    os.Stdin = stdin

    stdout, err := os.OpenFile("output.txt",
        os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
    if err != nil {
        log.Fatal(err)
    }
    os.Stdout = stdout

    fmt.Println("out")
    return
}

While Jeff Allen provided a good answer, there's a minor low-level "catch" to the approach presented there: the os.File values representing new destinations for the standard output streams will refer to file descriptors different from those of stdout and stderr as the OS sees them.1

The thing is, when a process starts on a POSIX-compatible system, it has its three standard streams open to file descriptors 0, 1 and 2 for stdin, stdout and stderr, correspondingly.

Hence in an obscure case of some bit of the code relying on the fact the standard streams being connected to the standard file descriptors, the code provided by Jeff Allen will not be quite correct.

To make it 100% correct, we may rely on another POSIX property which reuses the lowest free file descriptor when opening a new file. Hence if we close the file representing one standard stream and immediately open another one,—that new file will be open using the file descriptor of the just closed standard stream. To guarantee that no file is open between the sequence of steps just presented, we must run our code before any goroutine except the main one starts running—that is, in main() or any init().

Here's the code to demonstrate the idea:

package main

import (
    "os"
)

func init() {
    err := os.Stdout.Close()
    if err != nil {
        panic(err)
    }

    fd, err := os.OpenFile("output.txt",
        os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
    if err != nil {
        panic(err)
    }

    os.Stdout = fd
}

func main() {
    myfd := os.NewFile(1, "")

    _, err := myfd.Write([]byte("Direct output to FD 1
"))
    if err != nil {
        panic(err)
    }

    _, err = os.Stdout.Write([]byte("Write to redirected os.Stdout
"))
    if err != nil {
        panic(err)
    }

    os.Exit(1)
}

…and here is how it works:

$ go build
$ ./freopen 
$ cat output.txt 
Direct output to FD 1
Write to redirected os.Stdout

It might seem like nitpicking but I think it worth explaning the "full stack" of what's going on.

Oh, and redirecting this way will also provide sensible view to the process for outside observers: say, on Linux, inspecting the file descriptors opened by the process via something like

$ vdir /proc/<pid>/fd

will provide sensible results.


1 …and everything else which does not know about Go—for instance, a bit of linked in C code which calls something like write(1, "OK ", 3);