La lettura e la scrittura di file sono tecniche assolutamente fondamentali per la gestione dei dati. Molto spesso, quando dovremo analizzare file o condividere i risultati delle nostre elaborazioni, ricorreremo proprio al salvataggio persistente di informazioni su filesystem ed il linguaggio Go metterà, come sempre, tutto il necessario a disposizione. Iniziamo una carrellata di esempi che permetterà di evidenziare le casistiche più comuni.
Lettura di file di testo
Per eseguire l'interazione con il filesystem si ricorre al package os
che contiene tutta una serie di strumenti per l'interazione con file e cartelle nonché per la gestione dei processi e delle attività in esecuzione. Supponiamo di avere un file di testo che useremo in questi casi come banco di prova. Si chiama nota.txt
ed ha il seguente contenuto:
Appunti per una gita:
- portare palla
- portare pranzo
- portare costume
Qualora volessimo leggerne il contenuto per intero potremmo fare questo:
package main
import (
"fmt"
"os"
)
func main() {
testo, errore := os.ReadFile("nota.txt")
if errore != nil {
fmt.Println("Lettura non possibile!")
os.Exit(1)
}
fmt.Println(string(testo))
}
Notiamo che il package os
è stato impiegato in due modi:
- abbiamo invocato la funzione
ReadFile
per eseguire una lettura dell'intero contenuto. Notiamo che, a differenza di altre tecniche comuni, non si è fatta una generica open in cui specificare la modalità con cui aprire una connessione al file (lettura, scrittura, aggiunta di dati) ma questo strumento è nativamente dedicato alla lettura del testo; - abbiamo utilizzato
Exit
per uscire dal programma in caso di errore. Vediamo infatti cheReadFile
restituisce due valori di cui il primo è l'eventuale contenuto mentre il
secondo rappresenta un errore in accesso nel caso in cui l'operazione non sia possibile.
Tale modalità risulta utile nel caso in cui si voglia leggere tutto il contenuto, ad esempio, per eseguire un'elaborazione sul testo o i valori in esso contenuti. Ma qualora fossimo interessati alla scansione riga per riga del file - supponiamo per eseguire una ricerca - dovremmo impostare il lavoro diversamente. Vediamo come:
package main
import (
"bufio"
"fmt"
"strings"
"os"
)
func main() {
file_aperto, errore := os.Open("nota.txt")
if errore != nil {
fmt.Println("Lettura non possibile!")
os.Exit(1)
}
lettore_righe := bufio.NewScanner(file_aperto)
for lettore_righe.Scan() {
riga:=lettore_righe.Text()
if strings.HasPrefix(riga, "-") {
fmt.Println(riga[2:])
}
}
}
In questo esempio, abbiamo voluto ottenere solo la lista di ciò che non vogliamo dimenticare in vista di una gita producendo questo output:
portare palla
portare pranzo
portare costume
Nel nostro codice, abbiamo usato ancora il package os
facendo leva però sul suo metodo Open
. Questo ha prodotto una connessione al file che è stato poi scansionato con un
oggetto Scanner
.
L'aspetto interessante di questa funzionalità è che permette di leggere il file riga per riga dandoci modo di analizzare il contenuto di ognuna di esse. Abbiamo stampato solo le righe
che iniziano con un trattino rimuovendolo però prima di inviarle in output (avendo scritto noi il file ci siamo voluti fidare del formato). La stampa della stringa dal terzo carattere in poi è stata prodotta con riga[2:]
.
Scrittura di file di testo
Per quanto riguarda la scrittura di file, ripercorreremo all'incirca i medesimi passaggi della sezione precedente. Prima di tutto vedremo come salvare una stringa su file.
Nell'esempio tratteremo una breve sequenza di caratteri ma solo a scopo dimostrativo. Tuttavia, come si può immaginare, nella realtà si potranno salvare tutte le informazioni necessarie all'interno del file che si sta creando.
package main
import (
"os"
"fmt"
)
func main() {
da_salvare := "Questo è il messaggio che vogliamo ricordare..."
errore := os.WriteFile("memo.txt", []byte(da_salvare), 0644);
if errore != nil {
fmt.Println("Scrittura non possibile!")
os.Exit(1)
}
fmt.Println("Scrittura eseguita")
}
Questa tecnica risulta utile quando abbiamo in una stringa dei contenuti da salvare in un nuovo file. La stringa in questo modo diventerà il contenuto totale del file.
L'operazione centrale in tutto questo è WriteFile
che richiede, oltre al nome del nuovo file da creare, il contenuto della stringa convertito in una sequenza di byte. Come si vede, questa operazione preliminare non ha comportato grossi problemi in quanto è stato sufficiente passare l'argomento come []byte(da_stampare)
.
Per il resto, l'esempio segue le casistiche precedenti: abbiamo sfruttato il package os
e la funzione attivata ha restituito un codice di errore.
In quest'ultimo esempio della lezione, abbiamo scritto un file riga per riga - altra opzione estremamente importante - ricorrendo ad una funzionalità del package fmt
ovvero
Fprintln
che permetterà di salvare una stringa in un file con la semplicità con cui stampiamo a video:
package main
import (
"fmt"
"os"
)
var da_salvare = []string{
"portare palla",
"portare pranzo",
"portare costume",
}
func main() {
file_aperto, errore := os.Create("memo_gita.txt")
if errore != nil {
fmt.Println("Scrittura non possibile!")
os.Exit(1)
}
fmt.Fprintln(file_aperto, "Appunti per una gita:")
for _, riga := range da_salvare {
fmt.Fprintln(file_aperto, "- ", riga)
}
}
Dopo l'esecuzione, il file memo_gita.txt
avrà il seguente contenuto:
Appunti per una gita:
- portare palla
- portare pranzo
- portare costume
Abbiamo proceduto alla stampa di un array di stringhe (come sempre, immaginiamolo come il risultato di una qualche elaborazione) dove ognuna di esse è stata arricchita con l'aggiunta di un trattino iniziale con fmt.Fprintln(file_aperto, "- ", riga)
.
La scansione dell'array è stata sviluppata rapidamente con un ciclo e prima di esso abbiamo apposto il titolo al nostro elenco come prima riga del file.
In questo esempio, abbiamo usato anche il package os
ma solo per le operazioni di creazione del file (Create
) e di uscita dal programma in caso di errore (Exit
).