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

Mat*_*hew 6 error-handling file-io haskell

我正在Haskell中编写一个应用程序,如果失败readFile或想要向用户显示有意义的错误消息writeFile.我正在IOError使用Control.Exception.tryJust并将它们转换为人类可读的文本.

但是,我无法弄清楚应该捕获哪些错误以及如何从中提取信息.例如,假设"/ bin"是一个目录而"/ bin/ls"是一个文件,readFile "/bin"并且readFile "/bin/ls/asdf"两者都给出"不合适的类型",但(在我看来)它们是不同的错误.在第一个的情况下,我可以通过处理目录中的每个文件来恢复,而第二个更像是"不存在"类型的错误.

与前面的例子相比,似乎没有一种可移植的方式来捕获"不适当类型"错误.看GHC.IO.Exception,InappropriateType只标记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
                                            ]
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

Eri*_*ikR 4

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

在 OS X 下,如果您使用openFilefollow byhGetContents代替,readFile那么对于您提到的情况,您将得到不同的异常。

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

在 Linux 下,两个 open 调用都会抛出“不适当的类型”异常。ioe_errno但是,您可以通过和字段区分两者ioe_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
Run Code Online (Sandbox Code Playgroud)

ghci 会话示例:

*Main> bar "/bin"
errno: Nothing
description: is a directory
*Main> bar "/bin/ls/asd"
errno: Just 20
description: Not a directory
Run Code Online (Sandbox Code Playgroud)
  1. 一旦出现异常,我应该如何从中提取信息?

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

要将字段访问器纳入范围,您需要导入GHC.IO.Exception.

  1. 是否有一种可移植的方法来捕获仅 GHC 的异常,例如 InpropertyType?

正如 @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
Run Code Online (Sandbox Code Playgroud)