Fi

File in Elixir

2 exercises

About File

Functions for working with files are provided by the File module.

To read a whole file, use File.read/1. To write to a file, use File.write/2.

The module also provides file functions for copying, removing, renaming etc. Their names are similar to their UNIX equivalents, for example:

All the mentioned functions from the File module also have a ! variant that raises an error instead of returning an error tuple (e.g. File.read!/1). Use that variant if you don't intend to handle errors such as missing files or lack of permissions.

File.read("does_not_exist")
# => {:error, :enoent}

File.read!("does_not_exist")
# => ** (File.Error) could not read file "does_not_exist": no such file or directory
#        (elixir 1.10.4) lib/file.ex:353: File.read!/1

Files and processes

Every time a file is written to with File.write/2, a file descriptor is opened and a new Elixir process is spawned. For this reason, writing to a file in a loop using File.write/2 should be avoided.

Instead, a file can be opened using File.open/2. The second argument to File.open/2 is a list of modes, which allows you to specify if you want to open the file for reading or for writing.

Commonly-used modes are:

  • :read - open for reading, file must exist
  • :write - open for writing, file will be created if doesn't exist, existing content will be overwritten
  • :append - open for writing, file will be created if doesn't exist, existing content will be preserved

For a comprehensive list of all modes, see the documentation of File.open/2.

File.open/2 returns a PID of a process that handles the file. To read and write to the file, use functions from the IO module and pass this PID as the IO device.

When you're finished working with the file, close it with File.close/1.

file = File.open!("README.txt", [:write])
# => #PID<0.157.0>

IO.puts(file, "# README")
# => :ok

File.close(file)
# => :ok

Streaming files

Reading a file with File.read/1 is going to load the whole file into memory all at once. This might be a problem when working with really big files. To handle them efficiently, you might use File.open/2 and IO.read/2 to read the file line by line, or you can stream the file with File.stream/3. The stream implements both the Enumerable and Collectable protocols, which means it can be used both for reading and writing.

File.stream!("file.txt")
|> Stream.map(&(&1 <> "!"))
|> Stream.into(File.stream!("new_file.txt"))
|> Stream.run()

Paths

All the functions working on files require a file path. File paths can be absolute or relative to the current directory. For manipulating paths, use functions from the Path module. For cross-platform compatibility, use Path.join/1 to create paths. It will choose the platform-appropriate separator.

Path.expand(Path.join(["~", "documents", "important.txt"]))
"/home/user/documents/important.txt"
Edit via GitHub The link opens in a new window or tab

Learn File

Practicing is locked

Unlock 1 more exercise to practice File