Skip to content
AgileLogics
5 min read

Files and I/O

Prerequisites: Error handling

Writing and reading files

local f = assert(io.open("notes.txt", "w"))
f:write("first line\n", "second line\n")
f:close()

f = assert(io.open("notes.txt", "a"))
f:write("third line\n")
f:close()

f = assert(io.open("notes.txt", "r"))
local content = f:read("a")
f:close()
io.write(content)
print(#content)
first line
second line
third line
34
Mode Meaning
"r" read (the default)
"w" write — creates or truncates the file
"a" append
"rb", "wb" binary mode (matters on Windows)

f:read formats: "a" whole file, "l" next line, "n" a number, or a number of bytes.

Line by line

local f = assert(io.open("app.log", "w"))
f:write("INFO start\nERROR db timeout\nINFO request\nERROR disk full\n")
f:close()

local errors = 0
for line in io.lines("app.log") do
  if line:find("^ERROR") then
    errors = errors + 1
    print(line:sub(7))
  end
end
print(errors .. " errors")
db timeout
disk full
2 errors

io.lines(filename) opens the file, iterates over its lines, and closes it at the end — the simplest way to process a file.

Closing automatically

Lua 5.4 adds to-be-closed variables — the file is closed when the variable goes out of scope, even if an error occurs:

local function firstLine(path)
  local f <close> = assert(io.open(path))
  return f:read("l")
end

local out = assert(io.open("data.txt", "w"))
out:write("hello\nworld\n")
out:close()
print(firstLine("data.txt"))
hello

Other file operations

local f = assert(io.open("old.txt", "w")); f:close()
print(os.rename("old.txt", "new.txt"))
local h = io.open("new.txt")
print(h ~= nil)
h:close()
print(os.remove("new.txt"))
print(os.remove("new.txt"))
true
true
true
nil	new.txt: No such file or directory	2

Close files before renaming or deleting them — on Windows, an open file can't be removed.

Standard Lua has no function to list a directory — use the LuaFileSystem library (lfs.dir), or the host's API (Neovim has vim.fs, LÖVE has love.filesystem).