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/2
and IO.binwrite/2
functions from the IO
module:
iex> {:ok, file} = File.open "hello", [:write]
{:ok, #PID<0.47.0>}
iex> IO.binwrite file, "world"
:ok
iex> File.close file
:ok
iex> File.read "hello"
{:ok, "world"}
A file can also be opened with :utf8
encoding, which tells the File
module to interpret the bytes read from the file as UTF-8-encoded bytes.
Besides functions for opening, reading and writing files, the File
module has many functions to work with the file system. Those functions are named after their UNIX equivalents. For example, File.rm/1
can be used to remove files, File.mkdir/1
to create directories, File.mkdir_p/1
to create directories and all their parent chain. There are even File.cp_r/2
and File.rm_rf/1
to respectively copy and remove files and directories recursively (i.e., copying and removing the contents of the directories too).
You will also notice that functions in the File
module have two variants: one “regular” variant and another variant with a trailing bang (!
). For example, when we read the "hello"
file in the example above, we use File.read/1
. Alternatively, we can use File.read!/1
:
iex> File.read "hello"
{:ok, "world"}
iex> File.read! "hello"
"world"
iex> File.read "unknown"
{:error, :enoent}
iex> File.read! "unknown"
** (File.Error) could not read file unknown: no such file or directory
Notice that when the file does not exist, the version with !
raises an error. The version without !
is preferred when you want to handle different outcomes using pattern matching:
case File.read(file) do
{:ok, body} -> # do something with the `body`
{:error, reason} -> # handle the error caused by `reason`
end
However, if you expect the file to be there, the bang variation is more useful as it raises a meaningful error message. Avoid writing:
{:ok, body} = File.read(file)
as, in case of an error, File.read/1
will return {:error,reason}
and the pattern matching will fail. You will still get the desired result (a raised error), but the message will be about the pattern which doesn’t match (thus being cryptic in respect to what the error actually is about).
Therefore, if you don’t want to handle the error outcomes, prefer using File.read!/1
.