Monday, January 30, 2017

Elixir - File IO

File IO is an integral part of any programming language as it allows the language to interact with the files on the file system. In this chapter we'll have a look at 2 modules: Path and File.

The Path module

The path module is a very small module that can be considered as a helper module for filesystem operations. The majority of the functions in the File module expect paths as arguments. Most commonly, those paths will be regular binaries. The Path module provides facilities for working with such paths. Using functions from the Path module as opposed to just manipulating binaries is preferred since the Path module takes care of different operating systems transparently. Finally, keep in mind that Elixir will automatically convert slashes (/) into backslashes (\) on Windows when performing file operations.
Let us look at an example:
IO.puts(Path.join("foo", "bar"))
When running above program, it produces following result:
foo/bar
There are a lot of methods that the path module provides. You can have a look at these here. These methods would be frequently used if you are performing many file manipulation operations.

The File module

The File module contains functions that allow us to open files as IO devices. By default, files are opened in binary mode, which requires developers to use the specific IO.binread and IO.binwrite functions from the IO module. Let us create a file called newfile and write some data to it:
{:ok, file} = File.read("newfile", [:write]) 
# Pattern matching to store returned stream
IO.binwrite(file, "This will be written to the file")
If you go to open the file we just wrote into, you'll get its contents as:
This will be written to the file
Let us have a look at how to use the file module.

Opening a file

To open a file, we can use any one of the following 2 functions:
{:ok, file} = File.open("newfile")
file = File.open!("newfile")
There is a difference between the above 2 calls to open and open! functions. The File.open function always returns a tuple. If file is successfully opened it returns the first value in the tuple as :ok and second value is literal of type io_device. If an error is caused, it'll return a tuple with first value as :error and second value as the reason. The File.open!() function on the other hand would return a io_device if file is successfully opened else it'll raise an error. NOTE: This is the pattern followed in all of the file module functions we are going to discuss.
We can also specify the modes in which we want to open this file. To open a file as read only and in utf-8 encoding mode, we use the following code:
file = File.open!("newfile", [:read, :utf8])

Writing to a file

We have 2 ways to write to files. Let us see the first one using the write function from the File module.
File.write("newfile", "Hello")
But this should not be used if you are making multiple writes to the same file. Every time this function is invoked, a file descriptor is opened and a new process is spawned to write to the file. If you are doing multiple writes in a loop, open the file via File.open and write to it using the methods in IO module. Let us see an example:
#Open the file in read, write and utf8 modes. 
file = File.open!("newfile_2", [:read, :utf8, :write])

#Write to this "io_device" using standard IO functions
IO.puts(file, "Random text")
You can use other IO module methods like IO.write and IO.binwrite to write to files opened as io_device.

Reading from a file

We have 2 ways to read from files. Let us see the first one using the read function from the File module.
IO.puts(File.read("newfile"))
When running this code, you should get a tuple with the first element as :ok and second one as the contents of newfile.
We can also use the File.read! function to just get the contents of the files returned to us.

Closing an open file

Whenever you open a file using the File.open function, after you are done using it, you should close it using the File.close function:
File.close(file)

No comments:

Post a Comment