File I/O in Lua
Lua fornisce due approcci per lavorare con i file: il modello semplice tramite la libreria io e il modello completo tramite handle di file espliciti. In questa guida esploreremo entrambi, con esempi pratici per scenari reali.
Apertura dei File con io.open
La funzione io.open() apre un file e restituisce un handle (oggetto file) che permette di leggere o scrivere. Se l’operazione fallisce, restituisce nil seguito da un messaggio di errore.
local file, errore = io.open("dati.txt", "r")
if not file then
print("Errore nell'apertura: " .. errore)
else
-- operazioni sul file
file:close()
end
Modalita’ di Apertura
| Modalita’ | Descrizione |
|---|---|
"r" |
Lettura (default). Il file deve esistere. |
"w" |
Scrittura. Crea il file o sovrascrive il contenuto. |
"a" |
Append. Aggiunge alla fine del file esistente. |
"rb" |
Lettura binaria. |
"wb" |
Scrittura binaria. |
"ab" |
Append binario. |
"r+" |
Lettura e scrittura. Il file deve esistere. |
"w+" |
Lettura e scrittura. Crea o sovrascrive. |
Lettura dei File
Lua offre diversi formati di lettura tramite il metodo file:read().
Leggere l’Intero File
local file = io.open("poesia.txt", "r")
if file then
local contenuto = file:read("*a") -- legge tutto il contenuto
print(contenuto)
file:close()
end
Leggere Riga per Riga
local file = io.open("lista.txt", "r")
if file then
local riga = file:read("*l") -- legge una riga (senza newline)
while riga do
print(riga)
riga = file:read("*l")
end
file:close()
end
Leggere un Numero
local file = io.open("numeri.txt", "r")
if file then
local numero = file:read("*n") -- legge un numero
while numero do
print("Numero letto: " .. numero)
numero = file:read("*n")
end
file:close()
end
Leggere un Numero Specifico di Byte
local file = io.open("dati.bin", "rb")
if file then
local blocco = file:read(1024) -- legge 1024 byte
while blocco do
print("Letti " .. #blocco .. " byte")
blocco = file:read(1024)
end
file:close()
end
Scrittura dei File
Il metodo file:write() scrive stringhe nel file. Non aggiunge automaticamente un carattere di nuova riga.
local file = io.open("output.txt", "w")
if file then
file:write("Prima riga\n")
file:write("Seconda riga\n")
file:write("Valore: " .. tostring(42) .. "\n")
file:close()
print("File scritto con successo!")
end
Scrittura con Append
-- Aggiunge contenuto senza cancellare quello esistente
local file = io.open("log.txt", "a")
if file then
file:write(os.date("[%Y-%m-%d %H:%M:%S] ") .. "Evento registrato\n")
file:close()
end
L’Iteratore io.lines
io.lines() fornisce un modo elegante per iterare sulle righe di un file. Il file viene chiuso automaticamente alla fine dell’iterazione.
-- Modo diretto con io.lines
for riga in io.lines("configurazione.txt") do
print(riga)
end
-- Con un handle di file aperto
local file = io.open("dati.txt", "r")
if file then
for riga in file:lines() do
print(riga)
end
file:close()
end
Gestione degli Errori
Una gestione robusta degli errori e’ fondamentale quando si lavora con i file.
Pattern Base
local function leggi_file_sicuro(percorso)
local file, errore = io.open(percorso, "r")
if not file then
return nil, "Impossibile aprire '" .. percorso .. "': " .. errore
end
local contenuto, err_lettura = file:read("*a")
file:close()
if not contenuto then
return nil, "Errore nella lettura: " .. (err_lettura or "sconosciuto")
end
return contenuto
end
local testo, err = leggi_file_sicuro("miofile.txt")
if testo then
print("Contenuto: " .. testo)
else
print("Errore: " .. err)
end
Uso di pcall per Sicurezza
local ok, risultato = pcall(function()
local file = assert(io.open("importante.txt", "r"))
local contenuto = file:read("*a")
file:close()
return contenuto
end)
if ok then
print("Letto con successo: " .. risultato)
else
print("Errore protetto: " .. risultato)
end
Esempio Pratico: Leggere un File di Configurazione
local function carica_config(percorso)
local config = {}
local file = io.open(percorso, "r")
if not file then
print("File di configurazione non trovato, uso valori predefiniti")
return config
end
for riga in file:lines() do
-- Ignora righe vuote e commenti
if riga ~= "" and not riga:match("^#") then
local chiave, valore = riga:match("^(%w+)%s*=%s*(.+)$")
if chiave and valore then
-- Prova a convertire in numero
local num = tonumber(valore)
if num then
config[chiave] = num
elseif valore == "true" then
config[chiave] = true
elseif valore == "false" then
config[chiave] = false
else
config[chiave] = valore
end
end
end
end
file:close()
return config
end
-- File config.txt:
-- # Configurazione server
-- host = localhost
-- porta = 8080
-- debug = true
-- nome = MioServer
local cfg = carica_config("config.txt")
for k, v in pairs(cfg) do
print(k .. " = " .. tostring(v) .. " (" .. type(v) .. ")")
end
Esempio Pratico: Sistema di Logging
local Logger = {}
function Logger.nuovo(percorso_file)
local self = {percorso = percorso_file}
function self.scrivi(livello, messaggio)
local file = io.open(self.percorso, "a")
if file then
local timestamp = os.date("%Y-%m-%d %H:%M:%S")
file:write(string.format("[%s] [%s] %s\n", timestamp, livello, messaggio))
file:close()
end
end
function self.info(messaggio)
self.scrivi("INFO", messaggio)
end
function self.errore(messaggio)
self.scrivi("ERRORE", messaggio)
end
function self.avviso(messaggio)
self.scrivi("AVVISO", messaggio)
end
function self.leggi_tutto()
local file = io.open(self.percorso, "r")
if file then
local contenuto = file:read("*a")
file:close()
return contenuto
end
return ""
end
return self
end
-- Utilizzo
local log = Logger.nuovo("applicazione.log")
log.info("Applicazione avviata")
log.avviso("Memoria al 80%")
log.errore("Connessione al database fallita")
print(log.leggi_tutto())
Esempio Pratico: Elaborazione CSV
local function leggi_csv(percorso, separatore)
separatore = separatore or ","
local dati = {}
local file = io.open(percorso, "r")
if not file then
return nil, "File non trovato"
end
local intestazioni = nil
for riga in file:lines() do
local campi = {}
for campo in riga:gmatch("[^" .. separatore .. "]+") do
table.insert(campi, campo:match("^%s*(.-)%s*$")) -- trim
end
if not intestazioni then
intestazioni = campi
else
local record = {}
for i, intestazione in ipairs(intestazioni) do
record[intestazione] = campi[i]
end
table.insert(dati, record)
end
end
file:close()
return dati
end
local function scrivi_csv(percorso, dati, intestazioni, separatore)
separatore = separatore or ","
local file = io.open(percorso, "w")
if not file then
return false, "Impossibile creare il file"
end
-- Scrivi intestazioni
file:write(table.concat(intestazioni, separatore) .. "\n")
-- Scrivi dati
for _, record in ipairs(dati) do
local valori = {}
for _, intestazione in ipairs(intestazioni) do
table.insert(valori, tostring(record[intestazione] or ""))
end
file:write(table.concat(valori, separatore) .. "\n")
end
file:close()
return true
end
-- Esempio di utilizzo
local studenti = leggi_csv("studenti.csv")
if studenti then
for _, s in ipairs(studenti) do
print(s.nome .. " - " .. s.voto)
end
end
Conclusione
La gestione dei file in Lua e’ semplice ma efficace. Il modello basato su io.open() e handle di file offre controllo completo sulle operazioni di lettura e scrittura. E’ fondamentale chiudere sempre i file dopo l’uso e gestire correttamente gli errori con controlli su nil. L’iteratore io.lines() semplifica l’elaborazione riga per riga, mentre i diversi formati di file:read() permettono di gestire testo, numeri e dati binari.