当 stdout 被重定向时,作为文件写入 /dev/stdout 失败并显示 Errno::EACCES

Writing to /dev/stdout as a file fails with Errno::EACCES when stdout is redirected

我有一个遗产 gem 我正在使用它,给定一个文件名,知道如何写入文件,但不知道如何写入标准输出或任何其他形式的 IO。为了使其更加灵活,我试图通过传递 /dev/stdout 作为文件名来欺骗它写入标准输出。这有效,如果我 运行 在终端中调用 gem 的代码,并且不重定向输出。但是,如果我随后尝试将输出重定向到不同的文件或设备,它将失败并显示 Permission denied @ rb_sysopen - /dev/stdout (Errno::EACCES).

在 gem 的源代码中进行了一些实验和一些挖掘之后,我想出了以下重现问题的方法。鉴于此 Ruby 脚本 stdout.rb:

#!/usr/bin/env ruby

File.open('/dev/stdout', 'w+') do |f|
  f.puts('Hello, world')
end

(在这个例子中,我打开文件 w+ 因为这是遗留 gem 所做的,但它以与 a+ 相同的方式失败。)

我可以 运行 终端中的脚本,它有效:

$ ./stdout.rb
Hello, world

但是如果我将它重定向到一个文件,将它通过管道传输到另一个命令(例如 grep),或者尝试使用 echo $(./stdout.rb) 之类的东西捕获输出,它会失败:

$ ./stdout.rb > /tmp/hello.txt
Traceback (most recent call last):
        2: from ./stdout.rb:3:in `<main>'
        1: from ./stdout.rb:3:in `open'
./stdout.rb:3:in `initialize': Permission denied @ rb_sysopen - /dev/stdout (Errno::EACCES)

File.open() 调用似乎失败了。但是 redirect/capture 情况下的 /dev/stdout 权限与它只是写入终端时有什么不同?

(FWIW,这是在 macOS Catalina 10.15.5、Darwin 19.5.0 上运行的。)


更新: 如果我将模式从 w+ 更改为 w,它会起作用。我从 IEEE spec 了解到“+”表示“更新”,即阅读和写作。我不清楚为什么遗留 gem 认为它需要它,但更重要的是,为什么这在 TTY 中可行,但在重定向时却不行?

我认为答案在RubyIO::newdocumentation.

When the open mode of original IO is read only, the mode cannot be changed to be writable. Similarly, the open mode cannot be changed from write only to readable. When such a change is attempted the error is raised in different locations according to the platform.

w打开IO对象意味着:

"w" Write-only, truncates existing file to zero length or creates a new file for writing.

w+打开IO对象意味着:

"w+" Read-write, truncates existing file to zero length or creates a new file for reading and writing.

问题似乎是您无法使用 w+ 打开 stdout,因为权限是 write only。 问题是为什么只有在使用输出重定向时才会出现错误。

答案可能在我引用的前面的陈述中:

When such a change is attempted the error is raised in different locations according to the platform.

我建议在 Standard Library 中使用 StringIO 的对象,而不是 /dev/stdout 用于 'experiments'。

评论后添加

我现在意识到你需要传递一个字符串。

在这种情况下,我会尝试“/dev/tty”,我认为它可以工作,因为它还没有打开,只有写权限。

问题是您应该使用适当的 tty 而不是 GUI 中的模拟终端来查看结果,或者您应该找到一种方法将 tty 输出通过管道传输到另一个 IO。仅对 Unix 更重要,例如 OS.