File and IO

Elixir file operations, path manipulation, and IO (input/output) handling

File reading and writing

Reading files

# Read entire file (returns {:ok, binary} or {:error, reason})
case File.read("config.txt") do
  {:ok, contents} -> process(contents)
  {:error, :enoent} -> IO.puts("File not found")
  {:error, reason} -> IO.puts("Error: #{inspect(reason)}")
end

# Bang version (raises on error)
contents = File.read!("config.txt")

# Read as lines
File.read!("data.txt") |> String.split("\n")

# Stream lines (lazy, memory-efficient for large files)
File.stream!("log.txt")
|> Stream.filter(&String.contains?(&1, "ERROR"))
|> Stream.map(&String.trim/1)
|> Enum.take(100)

# Read line by line with File.stream! + Enumerable
File.stream!("data.csv")
|> CSV.decode!()
|> Enum.each(fn row -> process(row) end)

# Read specific line
File.read!("file.txt") |> String.split("\n") |> Enum.at(4)  # 0-indexed

# Read into UTF-8 string
{:ok, content} = File.read("file.txt")

Writing files

# Write entire file (overwrites existing)
File.write("output.txt", "Hello, World!")

# Append to file
File.write("log.txt", "New entry\n", [:append])

# Write list of strings (more efficient than concatenation)
File.write!("output.txt", ["Line 1\n", "Line 2\n", "Line 3\n"])

# Write binary data
File.write!("image.bin", <<0, 1, 2, 3>>)

File mode flags

Flag Meaning
:read Open for reading (default)
:write Open for writing
:append Open for appending
:exclusive Create new file, fail if exists
:binary Open in binary mode
:utf8 Open in UTF-8 mode
:sync Sync to disk on each write

Working with file handles

# Open file with explicit handle
{:ok, file} = File.open("data.txt", [:read, :utf8])
IO.readline(file)      # read one line
IO.read(file, :all)    # read entire contents
File.close(file)

# Using File.open with a function (auto-closes)
File.open("data.txt", [:read, :utf8], fn file ->
  IO.read(file, :all)
end)

Path manipulation

# Joining paths (OS-agnostic)
Path.join(["/home", "user", "documents", "file.txt"])
# => "/home/user/documents/file.txt"

# Extension and basename
Path.extname("photo.jpg")       # => ".jpg"
Path.basename("dir/photo.jpg")  # => "photo.jpg"
Path.basename("dir/photo.jpg", ".jpg")  # => "photo"
Path.dirname("/home/user/file.txt")     # => "/home/user"

# Expanding paths
Path.expand("~/documents")      # => "/home/user/documents"

# Type checking
File.regular?("file.txt")       # => true | false
File.dir?("/home/user")         # => true | false
File.exists?("/tmp")            # => true | false

Directory operations

# Create directory (and parents)
File.mkdir_p("/tmp/my/app/data")

# List directory
File.ls("/tmp")                 # => {:ok, ["file1", "dir2"]}

# List directory recursively
File.ls_r("/tmp/my")           # => {:ok, [list of all files/dirs]}

# Remove directory (must be empty)
File.rmdir("/tmp/empty_dir")

# Remove directory recursively
File.rm_rf("/tmp/my/app")      # => {:ok, [list of deleted files]}

File operations

# Copy
File.copy("source.txt", "dest.txt")

# Rename/move
File.rename("old.txt", "new.txt")

# Delete
File.rm("unwanted.txt")

# File stats
File.stat("file.txt")          # => {:ok, %File.Stat{...}}
File.stat!("file.txt").size    # => file size in bytes

# Touch (create empty file or update mtime)
File.touch!("new_file.txt")

# Change permissions
File.chmod("script.sh", 0o755)

IO module

# Standard output
IO.puts("Hello")              # with newline
IO.write("Hello")             # without newline
IO.inspect(%{a: 1})           # pretty-print any term

# Inspect with options
IO.inspect(data, label: "DEBUG", limit: 5, printable_limit: 20)

# Standard error
IO.puts(:stderr, "Error message")

# Standard input
line = IO.gets("Enter name: ")  # reads until newline

# Formatted output
:io.format("Hello ~s, you have ~B items~n", ["Alice", 5])

StringIO

Use an in-memory string as a file-like device:

{:ok, io} = StringIO.open("")
IO.write(io, "hello ")
IO.write(io, "world")
{:ok, {_, content}} = StringIO.contents(io)
content  # => "hello world"