[Wrote more about files John Goerzen **20070707190807] { hunk ./en/book-shortcuts.xml 43 +openBinaryFile"> hunk ./en/book-shortcuts.xml 53 +hPrint"> +hSeek"> +hTell"> +SeekMode"> +AbsoluteSeek"> +RelativeSeek"> +SeekFromEnd"> +hIsSeekable"> +stdin"> +stdout"> +stderr"> +openTempFile"> +openBinaryTempFile"> +System.Directory"> +System.IO"> +removeFile"> +renameFile"> +print"> hunk ./en/ch06-io.xml 175 - Working With Files + Working With Files and Handles + FIXME: deleting files, renaming them, directory contents hunk ./en/ch06-io.xml 190 - when working with files. + when working with files. There are "h" functions corresponding to + virtually all of the non-"h" functions; for instance, there is &print; + for printing to the screen and &hPrint; for printing to a file. hunk ./en/ch06-io.xml 328 + + While we are mostly working with text examples in this chapter, + binary files can also be used in Haskell. If you are working with a + binary file, you should use &openBinaryFile; instead of &openFile;. + Operating systems such as Windows process files differently if they + are opened as binary instead of as text. On operating systems such + as Linux, both &openFile; and &openBinaryFile; perform the same + operation. Nevertheless, for portability, it is still wise to always + use &openBinaryFile; if you will be dealing with binary data. + + + + + + Closing Handles + + You've already seen that &hClose; is used to close file handles. + Let's take a moment and think about why this is important. + + + As you'll see in , Haskell maintains + internal buffers for files. This provides an important performance + boost. However, it means that if you fail to &hClose; a file that is + open for writing, your data may not all be flushed out to disk until + you call &hClose;. + + + Another reason to make sure to &hClose; files is that open file take + up memory on the system. If your program runs for a long time, and + opens many files but fails to close them, it is conceivable that your + program could even crash due to resource exhaustion. + + + When a program exits, Haskell will normally take care of closing any + files that remain open. However, there are some circumstances in + which this may not happenIf there was a bug in the C + part of a hybrid program, for instance, so once + again, it is best to be responsible and call &hClose; all the time. + + + + + Seek and Tell + + When reading and writing from a file, the operating system maintains + an internal idea of the current position. Each time to do another + read, the operating system returns the next chunk of data that begins + at the current position, and increments the position to reflect the + data that you read. + + + You can use &hTell; to find out your current position in the file. + When the file is initially created, it is empty and your position + will be 0. After you write out 5 characters, your position will be + 5, and so on. &hTell; takes a &Handle; and returns an IO + Integer with your position. + + + The companion to &hTell; is &hSeek;. &hSeek; lets you reposition the + file position. It takes three parameters: a &Handle;, a &SeekMode;, + and a position. + + + &SeekMode; can be one of three different values, which specify how + the given position is to be interpreted. &AbsoluteSeek; means that + the position is a precise location in the file. This is the same + kind of information that &hTell; gives you. &RelativeSeek; means to + seek from the current position. A positive number requests going + forwards in the file, and a negative number means going backwards. + FIXME: do we need an example? + Finally, &SeekFromEnd; will seek to the specified number of bytes + before the end of the file. hSeek handle SeekFromEnd + 0 will take you to the end of the file. + + FIXME: do we need an example? + + Not all &Handle;s are seekable. A &Handle; usually corresponds to a + file, but it can also correspond to other things such as network + connections, tape drives, or terminals. You can use &hIsSeekable; to + see if a given &Handle; is seekable. + + + + + Standard Input, Output, and Error + + Earlier, I pointed out that for each non-"h" function, there is + usually also a corresponding "h" function that works on any &Handle;. + The non-"h" functions nothing more than shortcuts, in fact. + + + There are three pre-defined &Handle;s in + System.IO. These &Handle;s are always available + for your use. + + + They are &stdin;, which corresponds to standard input; &stdout; for + standard output; and &stderr; for standard error. Standard input + normally refers to the keyboard, standard output to the monitor, and + standard error also normally goes to the monitor. + + + Functions such as &getLine; can thus be trivially defined like this: + + +getLine = hGetLine stdin +putStrLn = hPutStrLn stdout +print = hPrint stdout + + + Earlier, I told you what the three standard file handles "normally" + correspond to. That's because some operating systems let you + redirect the file handles to come from (or go to) different places -- + files, devices, or even other programs. This feature is used + extensively in shell scripting on POSIX (Linux, BSD, Mac) + operating systems, but can + also be used on Windows. + + + It often makes sense to use standard input and output instead of + specific files. This lets you interact with a human at the terminal. + But it also lets you work with input and output files -- or even + combine your code with other programs -- if that's + what's requested. + + + As an example, we can provide input to + callingpure.hs in advance like this: + + +$ echo John | runhaskell callingpure.hs +Greetings once again. What is your name? +Pleased to meet you, John. +Your name contains 4 characters. + + FIXME: does this work on windows? + + While callingpure.hs was running, it did not wait + for input at the keyboard; instead it received + John from the echo program. + Notice also that the output didn't contain the word + John on a separate line as it did when this + program was run at the keyboard. The terminal normally echoes + everything you type back to you, but that is technically input, and + this not included in the output stream. + + + + + Deleting and Renaming Files + + So far in this chapter, we've discussed the contents of the files. + Let's now talk a bit about the files themselves. + + + &System.Directory; provides two functions you may find useful. + &removeFile; takes a single argument, a filename, and deletes that + file.POSIX programmers may be interested to know that + this corresponds to unlink() in + C. &renameFile; takes two filenames: the first + is the old name and the second is the new name. If the new filename + is in a different directory, you can also think of this as a move. + The old filename must exist prior to the call to &renameFile;. If + the new file already exists, it is removed before the rename takes + place. + + + There are many other functions in &System.Directory; for doing things + such as creating and removing directories, finding lists of files in + directories, and testing for file existance. These are discussed in + FIXME: add ref to appropriate section of chapter 19. + + + + + Temporary Files + + Programmers frequently have a need for temporary files. These files + may be used to store large amounts of data needed for computations, data to be + used by other programs, or any number of other uses. + + + While you could craft a way to manually open files with unique names, + the details of doing this in a secure way differ from platform to + platform. Haskell provides a convenient function called + &openTempFile; (and a corresponding &openBinaryTempFile;) to handle + the difficult bits for you. + + + &openTempFile; takes two parameters: the directory in which to create + the file, and a "template" for naming the file. The directory could + simply be "." for the current working directory. + Or you could use + System.Directory.getTemporaryDirectory to find the + best place for temporary files on a given machine. The template is used + as the basis for the file name; it will have some random characters + added to it to ensure that the result is truly unique. + + + The result of this function is IO (FilePath, + Handle). The first part of the tuple is the name of the + file created, and the second is a &Handle; opened in &ReadWriteMode; + over that file. When you're done with the file, you'll want to + &hClose; it and then call &removeFile; to delete it. See the + following example for a sample function to use. + + hunk ./en/ch06-io.xml 538 + + Extended Example: Functional I/O and Temporary Files + + asdf + + + hunk ./en/ch06-io.xml 547 - FIXME: stdin / stdout - FIXME: deleting files, renaming them, directory contents hunk ./en/ch06-io.xml 597 + + Buffering + FIXME + + hunk ./en/ch06-io.xml 615 - FIXME: buffering - FIXME: return + CWD addfile ./examples/ch06/tempfile.hs hunk ./examples/ch06/tempfile.hs 1 +{-- snippet all --} +-- ch06/tempfile.hs + +import System.IO +import System.Directory(getTemporaryDirectory, removeFile) +import System.IO.Error(catch) +import Control.Exception(finally) + +-- The main entry point. Work with a temp file in myAction. +main :: IO () +main = withTempFile "mytemp.txt" myAction + +{- The guts of the program. Called with the path and handle of a temporary + file. When this function exits, that file will be closed and deleted + because myAction was called from withTempFile. -} +myAction :: FilePath -> Handle -> IO () +myAction tempname temph = + do -- Start by displaying a greeting on the terminal + putStrLn "Welcome to tempfile.hs" + putStrLn $ "I have a temporary file at " ++ tempname + + -- Let's see what the initial position is + pos <- hTell temph + putStrLn $ "My initial position is " ++ show pos + + -- Now, write some data to the temporary file + let tempdata = show [1..10] + putStrLn $ "Writing one line containing " ++ + show (length tempdata) ++ " bytes: " ++ + tempdata + hPutStrLn temph tempdata + + -- Get our new position. This doesn't actually modify pos, + -- but makes the name "pos" correspond to a different value for + -- the remainder of the "do" block. + pos <- hTell temph + putStrLn $ "After writing, my new position is " ++ show pos + + -- Seek to the beginning of the file and display it + putStrLn $ "The file content is: " + hSeek temph AbsoluteSeek 0 + + -- hGetContents performs a lazy read of the entire file + c <- hGetContents temph + + -- Copy the file byte-for-byte to stdout, followed by \n + putStrLn c + + -- Let's also display it as a Haskell literal + putStrLn $ "Which could be expressed as this Haskell literal:" + print c + +{- This function takes two parameters: a filename pattern and another + function. It will create a temporary file, and pass the name and Handle + of that file to the given function. + + The temporary file is created with openTempFile. The directory is the one + indicated by getTemporaryDirectory, or, if the system has no notion of + a temporary directory, "." is used. The given pattern is passed to + openTempFile. + + After the given function terminates, even if it terminates due to an + exception, the Handle is closed and the file is deleted. -} +withTempFile :: String -> (FilePath -> Handle -> IO a) -> IO a +withTempFile pattern func = + do -- The library ref says that getTemporaryDirectory may raise on + -- exception on systems that have no notion of a temporary directory. + -- So, we run getTemporaryDirectory under catch. catch takes + -- two functions: one to run, and a different one to run if the + -- first raised an exception. If getTemporaryDirectory raised an + -- exception, just use "." (the current working directory). + tempdir <- catch (getTemporaryDirectory) (\_ -> return ".") + (tempfile, temph) <- openTempFile tempdir pattern + + -- Call (func tempfile temph) to perform the action on the temporary + -- file. finally takes two actions. The first is the action to run. + -- The second is an action to run after the first, regardless of + -- whether the first action raised an exception. This way, we ensure + -- the temporary file is always deleted. + finally (func tempfile temph) + (do hClose temph + removeFile tempfile) + +{-- /snippet all --} }