Windows上的Data.ByteString.Lazy.Char8换行符---文档是误导性的吗?

时间:2011-07-26 23:01:04

标签: haskell

我对bytestring库中的Data.ByteString.Lazy.Char8库有疑问。具体来说,我的问题涉及readFile函数,其记录如下:

  

懒惰地将整个文件读入ByteString。在Windows上使用“文本模式”来解释换行符

我对声称此功能将“在Windows上使用文本模式解释换行符”感兴趣。该函数的源代码如下:

-- | Read an entire file /lazily/ into a 'ByteString'. Use 'text mode'
-- on Windows to interpret newlines
readFile :: FilePath -> IO ByteString
readFile f = openFile f ReadMode >>= hGetContents

我们看到,从某种意义上说,文档中的声明完全正确:openFile函数(而不是openBinaryFile)已被使用,因此将启用换行符文件。

但是,该文件将被传递给hGetContents。这将调用Data.ByteString.hGetNonBlocking(请参阅源代码herehere),这是Data.ByteString.hGet的非阻止版本(请参阅the documentation); (最后)Data.ByteString.hGet来电GHC.IO.Handle.hGetBuf(请参阅the documentationthe source code)。这个函数的documentation表示

  

hGetBuf忽略Handle当前使用的任何TextEncoding,并直接从底层IO设备读取字节。

这表明我们使用readFile而非readBinaryFile打开文件这一事实无关紧要:数据将在不转换换行符的情况下被读取,尽管在开头提到的文档中声明了这一点。问题。

所以,问题的结论: 我错过了什么吗?是否有一种意义上的声明'那个Data.ByteString.Lazy.Char8.readFile在Windows上使用文本模式来解释换行符'是真的吗?或者文档只是误导?

P.S。测试还表明这个功能,至少在我使用时天真地使用它,在Windows上没有换行。

3 个答案:

答案 0 :(得分:4)

包裹维护人员Duncan Coutts FWIW回应了一些非常有帮助和启发性的评论。我已经要求他允许在这里发布它们,但在此期间这里是一个释义。

基本观点是文档曾经是正确的,但现在可能不是。特别是,当在Windows中打开文件时,操作系统本身允许您以“文本”或“二进制”模式打开它。 readFilereadBinaryFile 使用之间的区别是,在OS的文本模式下打开文件,在Win32上以二进制模式打开文件。 (他们都会在POSIX上做同样的事情。)重要的是,如果你在操作系统的二进制模式下打开一个文件,那么无法你可以从文件中读取而不进行换行:它发生了总是

当设置这样的内容时,问题中提到的文档是正确的--- Data.ByteString.Lazy.Char8.readFile将使用System.IO.readFile;这会告诉操作系统打开“文本”文件,即使正在使用hGetBuf,也会转换换行符。

然后,后来,Haskell的System.IO加强了对新行的处理更加灵活 - 特别是允许在POSIX操作系统上运行的Haskell版本,其中没有功能可以读取带有换行符修改的文件进入操作系统,但仍支持使用Windows风格的换行符读取文件;或更准确地说,在两个操作系统上都支持Python风格的'universal' newline conversion。这意味着:

  1. 新行的处理被带入Haskell库;
  2. 无论您使用的是readFile还是readBinaryFile,都会在Windows上以二进制模式始终打开文件;和
  3. 相反,readFilereadBinaryFile之间的选择会影响System.IO的库代码是否设置为nativeNewlineModenoNewlineTranslation。这将导致Haskell库转换为您执行适当的换行转换。您现在也可以选择要求universalNewlineMode
  4. 这与Haskell在System.IO中内置的正确编码支持几乎同时进行(而不是在输入上假设latin-1并简单地将输出Chars截断为前8位)。总的来说,这是一件好事。

    但是,关键的是,现在内置于库中的新换行符永远不会影响hPutBuf做什么 - 大概是因为建立新System.IO的人功能性认为,如果一个人以二进制方式阅读罚款,任何换行设置的换行都可能程序员想要的,即错误。事实上,它可能是99%的情况:但在这种情况下,它会导致上述问题: - )

    邓肯说,在未来的图书馆版本中,文档可能会改变以反映这个勇敢的新世界。在此期间,there is a workaround listed in another answer to this question.

答案 1 :(得分:2)

在源中再挖一层显示它确实读取了原始字节:

-- | 'hGetBuf' @hdl buf count@ reads data from the handle @hdl@
-- into the buffer @buf@ until either EOF is reached or
-- @count@ 8-bit bytes have been read.
-- It returns the number of bytes actually read.  This may be zero if
-- EOF was reached before any data was read (or if @count@ is zero).
--
-- 'hGetBuf' never raises an EOF exception, instead it returns a value
-- smaller than @count@.
--
-- If the handle is a pipe or socket, and the writing end
-- is closed, 'hGetBuf' will behave as if EOF was reached.
--
-- 'hGetBuf' ignores the prevailing 'TextEncoding' and 'NewlineMode'
-- on the 'Handle', and reads bytes directly.

hGetBuf :: Handle -> Ptr a -> Int -> IO Int
hGetBuf h ptr count
  | count == 0 = return 0
  | count <  0 = illegalBufferSize h "hGetBuf" count
  | otherwise = 
      wantReadableHandle_ "hGetBuf" h $ \ h_@Handle__{..} -> do
         flushCharReadBuffer h_
         buf@Buffer{ bufRaw=raw, bufR=w, bufL=r, bufSize=sz }
            <- readIORef haByteBuffer
         if isEmptyBuffer buf
            then bufReadEmpty    h_ buf (castPtr ptr) 0 count
            else bufReadNonEmpty h_ buf (castPtr ptr) 0 count

答案 2 :(得分:1)

对问题的答案不是很完整,但我想我会提到以下解决方法,以帮助遇到此问题并在Stack Overflow上找到此页面的其他人。它使用stringsearch包。

import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy.Search as S
import qualified System.IO
import Control.Monad

nativeCallsForConversion        = System.IO.nativeNewline == System.IO.CRLF
readFileUniversalNewlineConversion = let
  str_LF   = B.pack [10]
  str_CRLF = B.pack [13, 10]
  in liftM (S.replace str_CRLF str_LF) . L.readFile
readFileNativeNewlineConversion =
  if nativeCallsForConversion
  then readFileUniversalNewlineConversion
  else L.readFile