In today's episode, we're going to cover the remaining half of the File module. The functions we'll be going over are:

  • mkdir
  • mkdir_p
  • open
  • read
  • regular?
  • rm
  • rm_rf
  • rmdir
  • stat
  • stream!
  • stream_to!
  • touch
  • write
  • write_stat

Let's get started.

Setup

Just like last time, we'll make some files before beginning this episode:

rm /tmp/some_file*
rm /tmp/foo
rm -fr /tmp/some_dir*
rm -fr /tmp/nosuchdir
touch /tmp/some_file
touch /tmp/some_file_to_remove
mkdir /tmp/some_dir
touch /tmp/some_dir/file1
mkdir /tmp/some_dir2
echo "some data here
and some more
and more" > /tmp/some_file_text

Getting started

Alright, so let's just dive in, starting with mkdir

mkdir(path)

File.mkdir tries to create the directory path. Missing parent directories are not created. Returns the atom :ok if successful, or a tuple containing the atom :error as its first element, and a reason as the second element, if an error occurs.

iex(1)> File.mkdir("/tmp/some_dir2")
:ok
iex(2)> File.mkdir("/tmp/nosuchdir/some_dir2")
{:error, :enoent}

mkdir_p(path)

File.mkdir_p works the same way File.mkdir does, except it will also create any missing parent directories.

iex(3)> File.mkdir_p("/tmp/nosuchdir/some_dir2")
:ok

open(path, modes // [])

File.open with arity 2 opens the given path in the modes specified. By default, files are opened in binary mode. To open the file as a text file, just use mode :utf8. When opened in binary mode, the functions IO.binread/2 and IO.binwrite/2 are the only way you can interact with the file. When opened in text mode, all of the functions in IO work.

iex(14)> {:ok, file} = File.open("/tmp/some_file_text")
{:ok, #PID<0.59.0>}
iex(15)> IO.binread(file, :line)
"some data here\nand some more\nand more"
iex(16)> File.close(file)
:ok

open(path, modes, function)

File.open with arity 3 is my preferred way of opening up files. The third argument is a function that is invoked with the open file. The file is closed after the function runs, so it's impossible to leave a file around unclosed by accident.

iex(17)> {:ok, result} = File.open("/tmp/some_file_text", [:utf8], fn(file) -> IO.read(file, :line) end)
{:ok, "some data here\nand some more\nand more"

read(path)

File.read reads the file at the provided path and returns a tuple containing the atom :ok and the binary data as read from the file.

iex(18)> File.read("/tmp/some_file_text")
{:ok, "some data here\nand some more\nand more"

regular?(path)

File.regular? returns true if the file is a regular file.

iex(22)> File.regular?("/tmp/some_file_text")
true
iex(23)> File.regular?("/dev/tty0")
false

rm(path)

File.rm will attempt to remove the file at the provided path. It will return the atom :ok if successful, or a tuple containing the atom :error and the reason if unsuccessful.

iex(26)> File.rm("/tmp/nosuchfile")
{:error, :enoent}
iex(27)> File.rm("/tmp/some_file_to_remove")
:ok

rm_rf(path)

File.rm_rf removes files and directories recursively at the provided path. If successful, it returns a tuple containing the atom :ok and a list of all the files and directories removed. If unsuccessful, it returns a tuple containing the atom :error, the reason for the failure, and the file or directory the error occurred on.

iex(28)> File.rm_rf("/tmp/some_dir")
{:ok, ["/tmp/some_dir", "/tmp/some_dir/file1"]}

rmdir(path)

File.rmdir tries to delete the directory at the provided path.

iex(29)> File.rmdir("/tmp/some_dir2")
:ok

stat(path, opts // [])

File.stat returns information about the provided path. If it exists, it returns a tuple containing the atom :ok and a File.Info record. If it fails, it returns an error tuple.

iex(30)> File.stat("/tmp/some_file")
{:ok,
 File.Stat[size: 0, type: :regular, access: :read,
  atime: {{2014, 1, 2}, {7, 19, 58}}, mtime: {{2014, 1, 2}, {7, 19, 9}},
  ctime: {{2014, 1, 2}, {7, 19, 9}}, mode: 33033, links: 1, major_device: 2050,
  minor_device: 0, inode: 30539785, uid: 1000, gid: 1000]}

stream!(path, modes // [], line_or_bytes // :line)

File.stream! will turn a file's contents into a stream for each line.

iex(39)> File.stream!("/tmp/some_file_text") |> Stream.take(2) |> Enum.to_list
["some data here\n", "and some more\n"]

stream_to!(stream, path, modes)

iex(1)> File.stream!("/tmp/some_file_text") |> Stream.map(fn(x) -> String.replace(x, "o", "0") end) |> File.stream_to!("/tmp/some_new_file_text") |> Stream.run
:ok
iex(2)> File.read("/tmp/some_new_file_text")
{:ok, "s0me data here\nand s0me m0re\nand m0re\n"}

touch(path, time // :calendar.local_time)

File.touch updates the modification time (mtime) and the access time (atime) of the given file. F=The file is created if it doesn’t exist..

iex(3)> File.touch("/tmp/newb")
:ok

write(path, content, modes // [])

File.write is a simple way to write some content to disk:

iex(4)> File.write("/tmp/foo", "bar")
:ok
iex(5)> File.read("/tmp/foo")
{:ok, "bar"}

write_stat(path, stat, opts // [])

File.write_stat writes the given File.Stat back to the filesystem at the given path. Returns the atom :ok or a tuple containing the atom :error and a reason.

iex(12)> {:ok, stat} = File.stat("/tmp/foo")
{:ok,
 File.Stat[size: 3, type: :regular, access: :read_write,
  atime: {{2014, 1, 6}, {7, 34, 49}}, mtime: {{2014, 1, 6}, {7, 34, 39}},
  ctime: {{2014, 1, 6}, {7, 40, 20}}, mode: 33188, links: 1, major_device: 2050,
  minor_device: 0, inode: 30541297, uid: 1000, gid: 1000]}
iex(13)> File.write_stat("/tmp/foo", stat.gid(1001))
:ok
iex(14)> {:ok, stat} = File.stat("/tmp/foo")
{:ok,
 File.Stat[size: 3, type: :regular, access: :read_write,
  atime: {{2014, 1, 6}, {7, 34, 49}}, mtime: {{2014, 1, 6}, {7, 34, 39}},
  ctime: {{2014, 1, 6}, {7, 40, 44}}, mode: 33188, links: 1, major_device: 2050,
  minor_device: 0, inode: 30541297, uid: 1000, gid: 1001]}

Summary

That's it, we've covered the entire File module. There are a few esoteric things in there, but they all seem interesting. See you soon!

Resources