处理readFile和writeFile异常的正确方法

8
我正在使用Haskell编写一个应用程序,如果readFilewriteFile失败,我希望向用户显示有意义的错误消息。我目前使用Control.Exception.tryJust捕获IOError并将其转换为人类可读的文本。
然而,我不知道应该捕获哪些错误以及如何从中提取信息。例如,假设“/bin”是一个目录,“/bin/ls”是一个文件,readFile "/bin"readFile "/bin/ls/asdf"都会给出“不适当的类型”,但我认为它们是不同的错误。在第一种情况下,我可以通过处理目录中的每个文件来恢复,而第二种情况更像是一种“不存在”的错误类型。
与前面的例子相关,似乎没有一种便携式的方法来捕获“不适当的类型”错误。查看GHC.IO.ExceptionInappropriateType被标记为仅限于 GHC ,因此我不能在ioeGetErrorType上进行模式匹配。我可以在ioeGetErrorString上进行模式匹配,但我不确定这些字符串是否在不同的平台、编译器和区域设置中总是相同。
总之,我的问题如下:
1. 我应该捕获哪些异常以处理readFile/writeFile? 2. 一旦我有了一个异常,我应该如何提取其中的信息? 3. 是否存在一种便携式的方法来捕获仅适用于GHC的异常,例如InappropriateType
更新:
根据@ErikR的回答,我正在查看GHC.IO.Exception.IOException的字段,使用以下Haskell程序:
import Control.Exception (try)
import GHC.IO.Exception (IOException(..))
import qualified Data.ByteString as B


main :: IO ()
main = do
    try (readFile "/nonexistent") >>= printException
    try (writeFile "/dev/full" " ") >>= printException
    try (readFile "/root") >>= printException
    try (readFile "/bin") >>= printException
    try (writeFile "/bin" "") >>= printException
    try (readFile "/bin/ls/asdf") >>= printException
    try (writeFile "/bin/ls/asdf" "") >>= printException
    try (B.readFile "/dev/null") >>= printException

    -- I have /media/backups mounted as read-only. Substitute your own read-only
    -- filesystem for this one
    try (writeFile "/media/backups/asdf" "") >>= printException

printException :: Either IOError a -> IO ()
printException (Right _) = putStrLn "No exception caught"
printException (Left e) = putStrLn $ concat [ "ioe_filename = "
                                            , show $ ioe_filename e
                                            , ", ioe_description = "
                                            , show $ ioe_description e
                                            , ", ioe_errno = "
                                            , show $ ioe_errno e
                                            ]

在Debian Sid GNU/Linux上,使用GHC 7.10.3的输出结果如下:

ioe_filename = Just "/nonexistent", ioe_description = "No such file or directory", ioe_errno = Just 2
ioe_filename = Just "/dev/full", ioe_description = "No space left on device", ioe_errno = Just 28
ioe_filename = Just "/root", ioe_description = "Permission denied", ioe_errno = Just 13
ioe_filename = Just "/bin", ioe_description = "is a directory", ioe_errno = Nothing
ioe_filename = Just "/bin", ioe_description = "Is a directory", ioe_errno = Just 21
ioe_filename = Just "/bin/ls/asdf", ioe_description = "Not a directory", ioe_errno = Just 20
ioe_filename = Just "/bin/ls/asdf", ioe_description = "Not a directory", ioe_errno = Just 20
ioe_filename = Just "/dev/null", ioe_description = "not a regular file", ioe_errno = Nothing
ioe_filename = Just "/media/backups/asdf", ioe_description = "Read-only file system", ioe_errno = Just 30

1
在当前的Haskell基础设施状态下,“可移植”异常并不乐观。我们已经停滞在单编译器模式中多年了。希望这种情况很快会改变。 - dfeuer
1个回答

4
  1. 我应该为readFile/writeFile捕获哪些异常?

在OS X下,如果你使用openFile后跟hGetContents而不是readFile,那么你将会得到你提到的不同异常。

openFile "/bin/ls/asdf" ...会抛出“没有这个文件或目录”异常,而openFile "/bin" ...则会抛出“不适当的类型”异常。

在Linux下,两个open调用都会抛出“不适当的类型”异常。然而,你可以通过ioe_errnoioe_description字段区分它们:

import System.IO
import GHC.IO.Exception
import Control.Exception

foo path = do
  h <- openFile path ReadMode
  hClose h

show_ioe :: IOException -> IO ()
show_ioe e = do
  putStrLn $ "errno: " ++ show (ioe_errno e)
  putStrLn $ "description: " ++ ioe_description e

bar path = foo path `catch` show_ioe

示例ghci会话:

*Main> bar "/bin"
errno: Nothing
description: is a directory
*Main> bar "/bin/ls/asd"
errno: Just 20
description: Not a directory
  1. 我一旦有异常,该如何从中提取信息?

每个异常都有自己的结构。IOException的定义可以在这里找到。

要将字段访问器引入作用域,您需要导入GHC.IO.Exception

  1. 是否有一种便携式的方法来捕获仅限于GHC的异常,例如InappropriateType?

正如@dfeuer所说,实际上GHC是目前唯一的Haskell实现。

更新

运行程序后的结果。我没有包含最后一个结果,因为我周围没有只读文件系统进行测试,但我确定错误是相同的。

ioe_filename = Just "/nonexistent", ioe_description = "No such file or directory", ioe_errno = Just 2
ioe_filename = Just "/dev/full", ioe_description = "Permission denied", ioe_errno = Just 13
ioe_filename = Just "/root", ioe_description = "is a directory", ioe_errno = Nothing
ioe_filename = Just "/bin", ioe_description = "is a directory", ioe_errno = Nothing
ioe_filename = Just "/bin", ioe_description = "Is a directory", ioe_errno = Just 21
ioe_filename = Just "/bin/ls/asdf", ioe_description = "Not a directory", ioe_errno = Just 20
ioe_filename = Just "/bin/ls/asdf", ioe_description = "Not a directory", ioe_errno = Just 20
ioe_filename = Just "/dev/null", ioe_description = "not a regular file", ioe_errno = Nothing

我在GNU/Linux上使用GHC 7.10.3时,仍然从System.IO.openFile中获得“不适当的类型”错误。 - Matthew
我猜准确的异常情况取决于操作系统 - 我在OS X + GHC 7.10.2下进行了测试。已更新答案。 - ErikR
然而,您会收到略有不同的消息 - “不是目录”与“是目录” - 因此这是区分两者的一种方式。 - ErikR
谢谢您指引我使用ioe_description,这正是我所需要的。我已经更新了我的问题,并列出了我在GNU/Linux上捕获的字符串,但我无法访问OS X。您能否请检查它们在OS X上是否相同?再次感谢您的帮助! - Matthew
1
实际上,我会基于 ioe_errno 进行操作,因为在 Unix/Linux/OS X 系统中,描述字符串是 errno 错误代码的函数,并且 errno 代码更有可能相同。如果您提供一个生成错误的程序,我将在我的 OS X 系统上运行它。 - ErikR
我在我的问题中添加了一个Haskell程序。由于我不确定OS X在/dev/下有什么,以及/root是否存在,您可能需要更改一些路径。 - Matthew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接