Sizing Up our Files
Earlier this week we went over some basic mechanics with regard to binary files. This week we'll look at a couple functions for dealing with file size. These are perhaps a bit more useful with binary files, but they also work with normal files, as we'll see.
The two functions are very simple. We can get the file size, and we can set the file size:
hFileSize :: Handle -> IO Integer
hSetFileSize :: Handle -> Integer -> IO ()
Getting the file size does exactly what you would expect. It gives us an integer for the number of bytes in the file. We can use this on our bitmap from last time, but also on a normal text file with the lines "First Line" through "Fourth Line".
main :: IO ()
main = do
h1 <- openFile "pic_1.bmp" ReadMode
h2 <- openFile "testfile.txt" ReadMode
hFileSize h1 >>= print
hFileSize h2 >>= print
...
822
46
Note however, that we cannot get the file size of terminal handles, since these aren't, of course, files. A potential hope would be that this would return the number of bytes we've written to standard out so far, or the (strictly read) number of bytes we get in stdin
before end-of-file. But it throws an error instead:
main :: IO ()
main = do
hFileSize stdin >> print
hFileSize stdout >> print
...
<stdin>: hFileSize: inappropriate type (not a regular file)
Now setting the file size is also possible, but it's a tricky and limited operation. First of all, it will not work on a handle in ReadMode
:
main :: IO ()
main = do
h <- openFile "testfile.txt" ReadMode
hSetFileSize h 34
...
testfile.txt: hSetFileSize: invalid argument (Invalid argument)
In ReadWriteMode
however, this operation will succeed. By truncating from 46 to 34, we remove the final line "Fourth Line" from the file (don't forget the newline character!).
main :: IO ()
main = do
h <- openFile "testfile.txt" ReadMode
hSetFileSize h 34
... (File content)
First Line
Second Line
Third Line
Setting the file size also works with WriteMode
. Remember that opening a file in write mode will erase its existing contents. But we can start writing new contents to the file and then truncate later.
main :: IO ()
main = do
h <- openFile "testfile.txt" WriteMode
hPutStrLn h "First Line"
hPutStrLn h "Second Line"
hPutStrLn h "Third Line"
hPutStrLn h "Fourth Line"
hSetFileSize h 34
... (File content)
First Line
Second Line
Third Line
And, as you can probably tell by now, hSetFileSize
only truncates from the end of files. It can't remove content from the beginning. So with our binary file example, we could drop 48 bytes to remove one of the "lines" of the picture, but we can't use this function to remove the 54 byte header:
main :: IO ()
main = do
h <- openFile "pic_1.bmp" ReadWriteMode
hSetFileSize h 774
Finally, hSetFileSize
can also be used to add space to a file. Of course, the space it adds will all be null characters (byte = 0). But this can still be useful in certain circumstances.
main :: IO ()
main = do
h <- openFile "pic_1.bmp" ReadWriteMode
hSetFileSize h 870
inputBytes <- B.unpack <$> B.hGetContents h
let lines = chunksOf 48 (drop 54 inputBytes)
print (last lines)
...
[0,0,0,...]
These aren't the most common operations, but perhaps you'll find a use for them! We're almost done with our look at more obscure IO actions. If you've missed some of these articles and want a summary of this month's new material, make sure to subscribe to our monthly newsletter! You'll also get a sneak peak at what's coming next!