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:
File.cp/3
- copy a file.File.rm/1
- delete a file.File.rename/2
- rename and/or move a file.File.mkdir/1
- create a directory.File.cwd/0
- get the current working directory.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
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 preservedFor 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
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()
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"