我对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
(请参阅源代码here和here),这是Data.ByteString.hGet
的非阻止版本(请参阅the documentation); (最后)Data.ByteString.hGet
来电GHC.IO.Handle.hGetBuf
(请参阅the documentation或the source code)。这个函数的documentation表示
hGetBuf忽略Handle当前使用的任何TextEncoding,并直接从底层IO设备读取字节。
这表明我们使用readFile
而非readBinaryFile
打开文件这一事实无关紧要:数据将在不转换换行符的情况下被读取,尽管在开头提到的文档中声明了这一点。问题。
所以,问题的结论: 我错过了什么吗?是否有一种意义上的声明'那个Data.ByteString.Lazy.Char8.readFile在Windows上使用文本模式来解释换行符'是真的吗?或者文档只是误导?
P.S。测试还表明这个功能,至少在我使用时天真地使用它,在Windows上没有换行。
答案 0 :(得分:4)
基本观点是文档曾经是正确的,但现在可能不是。特别是,当在Windows中打开文件时,操作系统本身允许您以“文本”或“二进制”模式打开它。 readFile
和readBinaryFile
使用之间的区别是,在OS的文本模式下打开文件,在Win32上以二进制模式打开文件。 (他们都会在POSIX上做同样的事情。)重要的是,如果你在操作系统的二进制模式下打开一个文件,那么无法你可以从文件中读取而不进行换行:它发生了总是
当设置这样的内容时,问题中提到的文档是正确的--- Data.ByteString.Lazy.Char8.readFile
将使用System.IO.readFile
;这会告诉操作系统打开“文本”文件,即使正在使用hGetBuf
,也会转换换行符。
然后,后来,Haskell的System.IO
加强了对新行的处理更加灵活 - 特别是允许在POSIX操作系统上运行的Haskell版本,其中没有功能可以读取带有换行符修改的文件进入操作系统,但仍支持使用Windows风格的换行符读取文件;或更准确地说,在两个操作系统上都支持Python风格的'universal' newline conversion。这意味着:
readFile
还是readBinaryFile
,都会在Windows上以二进制模式始终打开文件;和readFile
和readBinaryFile
之间的选择会影响System.IO
的库代码是否设置为nativeNewlineMode
或noNewlineTranslation
。这将导致Haskell库转换为您执行适当的换行转换。您现在也可以选择要求universalNewlineMode
。这与Haskell在System.IO
中内置的正确编码支持几乎同时进行(而不是在输入上假设latin-1并简单地将输出Chars截断为前8位)。总的来说,这是一件好事。
但是,关键的是,现在内置于库中的新换行符永远不会影响hPutBuf
做什么 - 大概是因为建立新System.IO
的人功能性认为,如果一个人以二进制方式阅读罚款,任何换行设置的换行都可能不程序员想要的,即错误。事实上,它可能是99%的情况:但在这种情况下,它会导致上述问题: - )
答案 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