Menu

Leggere e scrivere file in Golang: os, bufio e directory

Come leggere e scrivere file in Go: os.ReadFile e os.WriteFile, leggere riga per riga con bufio.Scanner, aggiungere in coda con os.OpenFile, controllare se un file esiste e lavorare con le directory.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Leggere e scrivere un file intero

os.WriteFile e os.ReadFile coprono la maggior parte delle esigenze. Aprono, scrivono o leggono e chiudono con una sola chiamata.

Gli esempi di questa pagina lavorano in una directory temporanea creata con os.MkdirTemp e la rimuovono con defer os.RemoveAll(dir), così non lasciano niente dietro di sé. Nel tuo codice, un percorso relativo come "config.json" viene risolto rispetto alla directory di lavoro del processo, che non è necessariamente la directory del file sorgente o del binario.

os.WriteFile crea il file se serve e lo tronca se esiste. Il terzo argomento è il permesso Unix per un file appena creato: 0o644 significa che il proprietario può leggere e scrivere e tutti gli altri possono leggere. Viene ignorato per un file che esiste già, e la umask del processo può togliere dei bit.

os.ReadFile legge tutto in memoria. È la scelta giusta per i file di configurazione e gli input piccoli, e quella sbagliata per un log di diversi gigabyte.

Leggere riga per riga con bufio.Scanner

Per i file grandi, o quando ti servono comunque le righe, usa bufio.Scanner. Legge a blocchi e ti passa una riga alla volta, senza il carattere di a capo.

Tre dettagli:

  • Controlla sc.Err() dopo il ciclo. Scan restituisce false sia alla fine del file sia in caso di errore, e solo Err distingue i due casi.
  • Il limite di 64 KB per riga. Per impostazione predefinita una singola riga più lunga di 64 KB ferma lo scanner con bufio.Scanner: token too long. Per file con righe lunghe (JSON minificato, alcuni log), alza il limite prima del ciclo: sc.Buffer(make([]byte, 1024*1024), 10*1024*1024).
  • Altre unità. sc.Split(bufio.ScanWords) restituisce parole; bufio.ScanRunes restituisce caratteri.

Per leggere uno stream a blocchi di dimensione fissa invece che a righe, usa f.Read(buf) in un ciclo oppure io.Copy verso un altro writer.

Scrivere: os.Create, os.OpenFile e scrittura in coda

os.Create(name) apre un file in scrittura, creandolo o troncandolo. os.OpenFile dà il controllo completo tramite dei flag:

FlagSignificato
os.O_RDONLY, os.O_WRONLY, os.O_RDWRapre in lettura, in scrittura o in entrambe (scegline uno)
os.O_CREATEcrea il file se non esiste
os.O_TRUNCsvuota il file all'apertura
os.O_APPENDogni scrittura va in fondo
os.O_EXCLcon O_CREATE: fallisce se il file esiste già

os.Open(name) equivale a OpenFile(name, O_RDONLY, 0). os.Create(name) equivale a OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0o666).

Quando scrivi, l'errore di Close conta. Alcuni file system segnalano gli errori di scrittura solo alla chiusura, quindi un semplice defer f.Close() può nascondere dati persi. Per i file che scrivi, controlla Close esplicitamente come fa appendLine. Per i file che leggi soltanto, defer f.Close() va bene.

fmt.Fprintln e le altre funzioni di stampa di fmt accettano qualsiasi io.Writer, file compresi. Un bufio.Writer raggruppa in memoria le piccole scritture. Dimenticare w.Flush() è un bug classico: il programma termina normalmente e gli ultimi kilobyte non arrivano mai nel file.

Il file esiste?

Go non ha un os.Exists. Chiama os.Stat ed esamina l'errore:

errors.Is(err, fs.ErrNotExist) è l'idioma attuale. Sostituisce il vecchio os.IsNotExist(err), che non vede attraverso gli errori avvolti.

Controllare prima di aprire spesso non serve e crea una race: il file può comparire o sparire tra il controllo e l'apertura. Di solito lo apri e basta, gestendo il fs.ErrNotExist restituito dall'apertura. Per creare un file solo se non esiste ancora, usa O_CREATE|O_EXCL, che fa del controllo e della creazione un unico passaggio atomico.

Directory

OperazioneFunzione
creare una directoryos.Mkdir(path, 0o755)
creare un percorso con le directory genitricios.MkdirAll(path, 0o755)
elencare una directoryos.ReadDir(path)
attraversare un alberofilepath.WalkDir(root, fn)
eliminare un file o una directory vuotaos.Remove(path)
eliminare un alberoos.RemoveAll(path)
rinominare o spostareos.Rename(old, new)
file o directory temporaneaos.CreateTemp("", "prefix-*"), os.MkdirTemp("", "prefix")
unire parti di un percorsofilepath.Join(a, b, c)

Usa path/filepath per i percorsi del file system: usa il separatore giusto per il sistema operativo (\ su Windows). Il package path è per i percorsi separati da slash, come gli URL.

Go 1.24 ha aggiunto anche os.Root (os.OpenRoot(dir)), che apre file solo dentro una directory e rifiuta i percorsi che ne escono con .. o con link simbolici. Usalo quando i nomi dei file arrivano dagli utenti.

Errori comuni

  • Non controllare gli errori. Ognuna di queste chiamate può fallire. Un file che non si è riusciti ad aprire è nil, e ogni Read, Write o Close successivo su di esso restituisce invalid argument, il che nasconde la vera causa (il file mancava o il permesso è stato negato).
  • Dimenticare sc.Err() dopo un ciclo di scansione. Un errore di lettura sembra la fine del file.
  • Dimenticare Flush su un bufio.Writer. Manca la fine del file.
  • Ignorare l'errore di Close dopo una scrittura. Gli errori di scrittura possono emergere solo lì.
  • defer f.Close() dentro un ciclo su molti file. I file restano aperti finché la funzione non ritorna e puoi finire i descrittori di file. Sposta il corpo in una funzione, così ogni file si chiude a ogni iterazione.
  • Permessi scritti in decimale. 644 non è 0o644. Go legge 644 come numero decimale, che corrisponde a 0o1204 e imposta bit strani.

Domande frequenti

Come leggo un file intero in una stringa in Go?

data, err := os.ReadFile("notes.txt") restituisce il contenuto come []byte; convertilo con string(data). Apre, legge e chiude il file al posto tuo. Usalo per file che stanno comodamente in memoria; per i file grandi, leggi riga per riga con bufio.Scanner.

Come leggo un file riga per riga in Go?

Apri il file con os.Open, scrivi defer f.Close(), avvolgilo in bufio.NewScanner(f), cicla con for sc.Scan() { line := sc.Text() } e controlla sc.Err() dopo il ciclo. Le righe più lunghe di 64 KB fanno fallire lo scanner con token too long, a meno che tu non ne ingrandisca il buffer con sc.Buffer.

Come aggiungo contenuto in coda a un file in Go?

Aprilo con os.OpenFile(name, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644), scrivi e controlla l'errore di Close. O_CREATE crea il file se non esiste e O_APPEND fa andare ogni scrittura in fondo.

Come controllo se un file esiste in Go?

Chiama os.Stat(path) e verifica l'errore con errors.Is(err, fs.ErrNotExist). Un errore nil significa che esiste. Qualsiasi altro errore (per esempio permesso negato) significa che non puoi saperlo, quindi gestiscilo a parte invece di trattarlo come "non esiste".

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA