英文:
Is it possible to use "freopen"-like construction in Go?
问题
在C++中,有一个freopen
函数,它非常方便地用于通过stdin/out(cin/cout)读写文件。所以我决定在Go语言中找到类似的解决方案,但是我只找到了以下代码:
import "os"
os.Stdin, err = os.OpenFile("input.txt",
os.O_RDONLY | os.O_CREATE, 0666)
os.Stdout, err = os.OpenFile("output.txt",
os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0666)
但是,这段代码似乎不再起作用了!我错了吗?你知道其他的方法吗?
英文:
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?
答案1
得分: 4
虽然Jeff Allen提供了一个很好的答案,但是这种方法存在一个小的低级“陷阱”:表示标准输出流的os.File
值将引用与stdout和stderr的文件描述符不同的文件描述符,就操作系统而言。1
问题在于,当一个进程在一个兼容POSIX的系统上启动时,它的三个标准流会分别打开到文件描述符0、1和2,用于stdin、stdout和stderr。
因此,在某些代码依赖于标准流连接到标准文件描述符的情况下,Jeff Allen提供的代码可能不完全正确。
为了使其100%正确,我们可以依赖于另一个POSIX属性,即在打开新文件时重用最低的空闲文件描述符。因此,如果我们关闭表示一个标准流的文件,然后立即打开另一个文件,那么这个新文件将使用刚关闭的标准流的文件描述符打开。为了确保在刚刚提到的步骤序列之间没有打开的文件,我们必须在除了主goroutine之外的任何goroutine开始运行之前运行我们的代码,也就是在main()
或任何init()
中。
以下是演示这个思路的代码:
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\n"))
if err != nil {
panic(err)
}
_, err = os.Stdout.Write([]byte("Write to redirected os.Stdout\n"))
if err != nil {
panic(err)
}
os.Exit(1)
}
...这是它的工作原理:
$ go build
$ ./freopen
$ cat output.txt
Direct output to FD 1
Write to redirected os.Stdout
这可能看起来有点吹毛求疵,但我认为值得解释一下正在发生的“完整堆栈”。
哦,通过这种方式重定向还将为外部观察者提供合理的进程视图:例如,在Linux上,通过类似以下方式检查进程打开的文件描述符:
$ vdir /proc/<pid>/fd
将提供合理的结果。
1 ...以及其他不了解Go的东西,比如调用类似write(1, "OK\n", 3);
的链接C代码的一小部分
英文:
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.<sup>1<sup>
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\n"))
if err != nil {
panic(err)
}
_, err = os.Stdout.Write([]byte("Write to redirected os.Stdout\n"))
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.
<sup>1</sup> …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\n", 3);
答案2
得分: 1
你不能在另一个包中声明并赋值给一个变量(例如os.Stdin)。
但是以下代码可以正常工作:
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
}
英文:
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
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论