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.Scanrestituiscefalsesia alla fine del file sia in caso di errore, e soloErrdistingue 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.ScanRunesrestituisce 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:
| Flag | Significato |
|---|---|
os.O_RDONLY, os.O_WRONLY, os.O_RDWR | apre in lettura, in scrittura o in entrambe (scegline uno) |
os.O_CREATE | crea il file se non esiste |
os.O_TRUNC | svuota il file all'apertura |
os.O_APPEND | ogni scrittura va in fondo |
os.O_EXCL | con 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
| Operazione | Funzione |
|---|---|
| creare una directory | os.Mkdir(path, 0o755) |
| creare un percorso con le directory genitrici | os.MkdirAll(path, 0o755) |
| elencare una directory | os.ReadDir(path) |
| attraversare un albero | filepath.WalkDir(root, fn) |
| eliminare un file o una directory vuota | os.Remove(path) |
| eliminare un albero | os.RemoveAll(path) |
| rinominare o spostare | os.Rename(old, new) |
| file o directory temporanea | os.CreateTemp("", "prefix-*"), os.MkdirTemp("", "prefix") |
| unire parti di un percorso | filepath.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 ogniRead,WriteoClosesuccessivo su di esso restituisceinvalid 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
Flushsu unbufio.Writer. Manca la fine del file. - Ignorare l'errore di
Closedopo 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.
644non è0o644. Go legge644come numero decimale, che corrisponde a0o1204e 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".