为什么文件读取因STDIO或File.open而异?

Mat*_*ker 4 elixir

根据我是从中拉出:stdio还是打开文件,读取文件似乎表现不同.为什么?我希望能够从STDIN读取二进制文件或打开文件(File.open)并使用相同的代码来提取字节.

简而言之:

  • 为什么我在下面概述的行为在stdio和文件之间有所不同?
  • 我怎样才能实现目标?

测试用例

作为一个简单的测试用例,我有一个包含三个字节的二进制文件:

06 8C 7D
Run Code Online (Sandbox Code Playgroud)

我想要的结果是,从任何一个源读取此文件应该产生以下形式的二进制文件:

<<6, 140, 125>>
Run Code Online (Sandbox Code Playgroud)

但是,根据我是从STDIO读取还是打开文件,事情似乎有所不同.

以下是一系列演示行为的测试用例.

例1

stdio的IO.binread产生此错误

IO.inspect IO.binread(:stdio, 3)
$ elixir repro.exs < repro.bin
{:error, :collect_chars}
Run Code Online (Sandbox Code Playgroud)

例2

stdio的IO.read产生期望的结果

IO.inspect IO.read(:stdio, 3)
$ elixir repro.exs < repro.bin
<<6, 140, 125>>
Run Code Online (Sandbox Code Playgroud)

例3

IO.binread文件产生所需的结果

{:ok, file} = File.open("repro.bin")
IO.inspect IO.binread(file, 3)
$ elixir repro.exs
<<6, 140, 125>>
Run Code Online (Sandbox Code Playgroud)

例4

文件的IO.read增加了一个额外的字节(194),我不明白 - 我最好的猜测是这与utf8有关吗?

{:ok, file} = File.open("repro.bin")
IO.inspect IO.read(file, 3)
$ elixir repro.exs
<<6, 194, 140, 125>>
Run Code Online (Sandbox Code Playgroud)

我想要的是什么:

一种接受文件 stdio并同时处理任一设备的方法.现在,似乎我做不到.尽管我最好的谷歌搜索,我发现自己卡住了.

任何见解?

Mat*_*ker 5

JoséValim对elixir google小组的回答:

您的问题的答案是源的编码.STDIO默认为unicode,这意味着它不适合binread.这在binread函数中有记录,目前是Erlang错误/限制.要找出编码,请使用getopts:

iex> :io.getopts :standard_io [expand_fun: &IEx.Autocomplete.expand/1, echo: true, binary: true, encoding: :unicode]

另一方面,File处于latin状态,这意味着read将尝试转换,binread将返回原始字节.您可以尝试使用:io.setopts并查看是否获得了所需的结果:

iex> io.setopts :standard_io, encoding: :latin1

我知道情况并不理想.如果无论文件的编码如何,binread始终可以读取字节,这将是很好的.我在这里写了一篇报告:http://erlang.org/pipermail/erlang-bugs/2014-July/004498.html

总结一下:

  • read将始终尝试转换为设备编码
  • binread应该总是返回原始二进制文件,但是在unicode(这是IO设备的默认设置)方面存在一个错误

我看到的额外字节(194)的奇怪"注入"似乎是elixir/erlang试图将bin解释为utf8.

根据他的建议,直接设置stdio的编码似乎可以解决问题:

test_read = fn(device) ->
  IO.binread(device, 3)
end

#set stdio's encoding to latin1
:io.setopts(:standard_io, encoding: :latin1)

# Test the read against stdio
IO.inspect test_read.(:stdio)

#grab a file descriptor
{:ok, fd} = File.open("repro.bin")

# Test the same read against a file
IO.inspect test_read.(fd)
Run Code Online (Sandbox Code Playgroud)

输出:

$ elixir repro.exs < repro.bin
<<6, 140, 125>>
<<6, 140, 125>>
Run Code Online (Sandbox Code Playgroud)