Lua File I/O Guide — Reading, Writing, and Processing Files
In this tutorial, you will learn about Lua File I/O Guide. We cover key concepts, practical examples, and best practices to help you master this topic.
Lua file I/O uses the io library to read and write files through two models -- the simple model with implicit file handles for quick operations, and the full model for fine-grained control over reading and writing.
What You'll Learn
- Reading and writing text files with the simple model
- Using the full I/O model with explicit file handles
- Reading binary data
- Handling errors and edge cases
Why It Matters
File I/O is essential for configuration files, data processing, logging, and persistence. Durga Antivirus Pro uses Lua's file I/O to read malware signature databases and write scan reports. Game engines use file I/O to load level data, save game progress, and parse configuration files.
Real-World Use
A log analyzer reads Apache access logs line by line and aggregates statistics. A game saves player progress and inventory to a file. A configuration system reads .ini or .lua files at startup to configure application behavior.
flowchart LR
A["File I/O"] --> B["Simple Model"]
A --> C["Full Model"]
B --> D["io.open"]
C --> D
D --> E["Read"]
D --> F["Write"]
E --> G["Close"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
The Simple I/O Model
For quick read/write operations without managing file handles.
-- Reading entire file
local content = io.open("input.txt", "r"):read("*a")
print(content)
-- Writing to a file
local file = io.open("output.txt", "w")
file:write("Line 1\n")
file:write("Line 2\n")
file:close()
-- Appending to a file
local file = io.open("log.txt", "a")
file:write("New log entry\n")
file:close()
The Full I/O Model
More control with explicit file handles.
-- Opening a file for reading
local file, err = io.open("data.txt", "r")
if not file then
print("Error opening file: " .. err)
return
end
-- Reading modes
-- file:read("*a") -- read entire file
-- file:read("*l") -- read next line (default)
-- file:read("*n") -- read a number
-- file:read(n) -- read n bytes
-- Read line by line
local line = file:read()
while line do
print(line)
line = file:read()
end
-- Always close
file:close()
Read Modes
local file = io.open("data.txt", "r")
-- Read entire file
local all = file:read("*a")
print("File length: " .. #all)
file:seek("set", 0) -- rewind to beginning
-- Read as number
local num = file:read("*n")
print("First number: " .. num)
file:seek("set", 0)
-- Read specific bytes
local bytes = file:read(10)
print("First 10 bytes: " .. bytes)
file:close()
Write Modes and Buffering
local file = io.open("output.txt", "w")
file:write("First line\n")
file:write("Second line\n")
file:write("Third line\n")
-- Flush buffer explicitly (auto-flushed on close)
file:flush()
-- Set output buffer
file:setvbuf("line") -- line buffering
file:setvbuf("no") -- no buffering
file:setvbuf("full", 4096) -- full buffering with 4KB buffer
file:close()
Looping with file:lines
-- Most idiomatic way to read a file line by line
for line in io.lines("data.txt") do
print(line)
end
-- Equivalent with explicit handle
local file = io.open("data.txt", "r")
for line in file:lines() do
print(line)
end
file:close()
File Positioning
local file = io.open("data.txt", "r")
-- Get current position
local pos = file:seek()
print("Current position: " .. pos)
-- Read some data
local data = file:read(5)
print("Read: " .. data)
-- Seek relative to current position
file:seek("cur", 2) -- skip 2 bytes forward
-- Seek relative to end (negative offset)
local file_size = file:seek("end", 0)
print("File size: " .. file_size)
-- Seek absolute from beginning
file:seek("set", 0) -- rewind to start
file:close()
Binary File I/O
-- Reading binary data
local file = io.open("image.png", "rb")
-- "b" flag for binary mode (on Windows, prevents \n conversion)
local header = file:read(8) -- PNG signature
local _, _, width, height = string.unpack(
">I4I4",
file:read(8)
)
print("Width: " .. width .. ", Height: " .. height)
file:close()
-- Writing binary data
local out = io.open("output.bin", "wb")
local data = string.pack(">I4I4", 1920, 1080)
out:write(data)
out:close()
Reading CSV Files
local function parse_csv_line(line)
local fields = {}
local i = 1
while i <= #line do
if line:sub(i, i) == '"' then
-- Quoted field
i = i + 1
local start = i
while i <= #line do
if line:sub(i, i) == '"' then
if line:sub(i + 1, i + 1) == '"' then
i = i + 2
else
break
end
else
i = i + 1
end
end
table.insert(fields, line:sub(start, i - 1))
i = i + 2 -- skip closing quote and comma
else
-- Unquoted field
local comma = line:find(",", i)
if comma then
table.insert(fields, line:sub(i, comma - 1))
i = comma + 1
else
table.insert(fields, line:sub(i))
break
end
end
end
return fields
end
for line in io.lines("data.csv") do
local fields = parse_csv_line(line)
for j, field in ipairs(fields) do
print("Field " .. j .. ": " .. field)
end
end
Error Handling
-- Always check io.open return value
local file, err = io.open("nonexistent.txt", "r")
if not file then
print("Error: " .. err)
return
end
-- Check write operations
local success, err = file:write("data")
if not success then
print("Write error: " .. err)
end
-- Safely close with error handling
local success, err = file:close()
if not success then
print("Close error: " .. err)
end
Common Mistakes
1. Not closing files
Open files consume system resources. Always close files with file:close() or use io.lines which handles closing automatically.
2. Not checking io.open return value
io.open returns nil, error_message on failure. Without checking, the nil file handle causes "attempt to index a nil value" errors.
3. Confusing read modes
read("*a") reads all, read("*l") reads a line, read(5) reads 5 bytes. Mixing them up leads to wrong data.
4. Forgetting binary mode on Windows
On Windows, "r" mode converts \n to \r\n. Use "rb" for binary files to prevent data corruption.
5. Not setting file position after read
After reading part of a file, the position advances. If you need to re-read from the beginning, use file:seek("set", 0).
Practice Questions
1. What does io.open("file.txt", "r") return on failure?
It returns nil and an error message. Always check the first return value before using the file handle.
2. How do you read an entire file as a single string?
Use file:read("*a") after opening the file. Or io.open("file", "r"):read("*a") for simple cases.
3. What is the difference between io.lines and file:lines?
io.lines opens, reads, and closes the file automatically. file:lines requires an already-opened file handle.
Challenge: Write a function that reads a file, counts the number of lines, words, and characters, and returns them as a table.
FAQ
{{< faq question="What file modes does Lua support?" >}}
"r" read, "w" write (overwrites), "a" append, "r+" read/write, "w+" read/write (overwrites), "a+" read/append. Add "b" for binary mode on Windows.
{{< /faq >}}
{{< faq question="How do I check if a file exists?" >}}
Try to open it with io.open(file, "r"). If it returns nil, the file doesn't exist or can't be read. There's no dedicated file-exists function in standard Lua.
{{< /faq >}}
{{< faq question="Can I use Lua for binary file processing?" >}}
Yes. Use "rb" mode and string.pack/string.unpack for structured binary data. The string library provides byte-level operations.
{{< /faq >}}
{{< faq question="What is the maximum file size Lua can handle?" >}}
Lua can handle files up to the available memory limit, since read("*a") loads the entire file into memory. For larger files, read in chunks: file:read(4096) in a loop.
{{< /faq >}}
{{< faq question="Are file operations thread-safe in Lua?" >}} Standard Lua is single-threaded within a Coroutine/thread, so file operations within one Lua state are safe. Across multiple Lua states, use OS-level synchronization. {{< /faq >}}
Try It Yourself
-- Word count tool
local function word_count(filename)
local file = io.open(filename, "r")
if not file then
return nil, "Cannot open " .. filename
end
local lines = 0
local words = 0
local chars = 0
for line in file:lines() do
lines = lines + 1
chars = chars + #line + 1 -- +1 for newline
for _ in line:gmatch("%S+") do
words = words + 1
end
end
file:close()
return {lines = lines, words = words, chars = chars}
end
-- Test on this script
local stats = word_count("input.txt")
if stats then
print("Lines: " .. stats.lines)
print("Words: " .. stats.words)
print("Chars: " .. stats.chars)
end
What's Next
Now that you understand file I/O, learn about modules and how to organize Lua code into reusable components.
| Topic | Description | Link |
|---|---|---|
| Modules | require, module patterns, packages | {{< ref "10-modules" >}} |
| C API | Embedding and extending Lua in C | {{< ref "11-c-api" >}} |
| Python I/O | Compare with Python file handling | Python |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro