File
Le librerie standard di Go per la gestione dei file sono principalmente le seguenti:
- Libreria
os, responsabile dell'implementazione concreta dell'interazione con il filesystem del sistema operativo - Libreria
io, livello di astrazione per la lettura e scrittura IO - Libreria
fs, livello di astrazione per il filesystem
Questo articolo spiega come eseguire operazioni di base sui file nel linguaggio Go.
Apertura
I due modi comuni per aprire un file utilizzano due funzioni fornite dal pacchetto os. La funzione Open restituisce un puntatore a file e un errore:
func Open(name string) (*File, error)La seconda funzione OpenFile offre un controllo più granulare. La funzione Open è un semplice wrapper della funzione OpenFile:
func OpenFile(name string, flag int, perm FileMode) (*File, error)Vediamo prima il primo metodo di utilizzo. Basta fornire il nome del file corrispondente, come nel codice seguente:
func main() {
file, err := os.Open("README.txt")
fmt.Println(file, err)
}Il percorso di ricerca del file è per impostazione predefinita il percorso del file go.mod del progetto. Poiché non esiste un file README.txt nel progetto, verrà naturalmente restituito un errore:
<nil> open README.txt: The system cannot find the file specified.Poiché i tipi di errore IO sono molti, è necessario determinare manualmente se il file esiste. Allo stesso modo, il pacchetto os fornisce funzioni convenienti per questo scopo. Il codice modificato è il seguente:
func main() {
file, err := os.Open("README.txt")
if os.IsNotExist(err) {
fmt.Println("Il file non esiste")
} else if err != nil {
fmt.Println("Accesso al file anomalo")
} else {
fmt.Println("Lettura del file riuscita", file)
}
}Eseguendo nuovamente, l'output è:
Il file non esisteIn effetti, la prima funzione legge solo file in sola lettura e non può essere modificata:
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}Tramite la funzione OpenFile è possibile controllare più dettagli, come modificare i descrittori di file e i permessi dei file. Per quanto riguarda i descrittori di file, il pacchetto os fornisce le seguenti costanti:
const (
// Uno tra sola lettura, sola scrittura, lettura-scrittura deve essere specificato
O_RDONLY int = syscall.O_RDONLY // Apre il file in modalità sola lettura
O_WRONLY int = syscall.O_WRONLY // Apre il file in modalità sola scrittura
O_RDWR int = syscall.O_RDWR // Apre il file in modalità lettura-scrittura
// I valori rimanenti controllano il comportamento
O_APPEND int = syscall.O_APPEND // Quando si scrive sul file, i dati vengono aggiunti alla fine
O_CREATE int = syscall.O_CREAT // Crea il file se non esiste
O_EXCL int = syscall.O_EXCL // Usato con O_CREATE, il file non deve esistere
O_SYNC int = syscall.O_SYNC // Apre il file in modalità IO sincrona
O_TRUNC int = syscall.O_TRUNC // Tronca il file scrivibile quando aperto
)Per i permessi dei file sono fornite le seguenti costanti:
const (
ModeDir = fs.ModeDir // d: directory
ModeAppend = fs.ModeAppend // a: solo aggiunta
ModeExclusive = fs.ModeExclusive // l: esclusivo
ModeTemporary = fs.ModeTemporary // T: file temporaneo
ModeSymlink = fs.ModeSymlink // L: collegamento simbolico
ModeDevice = fs.ModeDevice // D: file dispositivo
ModeNamedPipe = fs.ModeNamedPipe // p: pipe con nome (FIFO)
ModeSocket = fs.ModeSocket // S: socket di dominio Unix
ModeSetuid = fs.ModeSetuid // u: setuid
ModeSetgid = fs.ModeSetgid // g: setgid
ModeCharDevice = fs.ModeCharDevice // c: dispositivo carattere Unix,前提是 impostato ModeDevice
ModeSticky = fs.ModeSticky // t: sticky bit
ModeIrregular = fs.ModeIrregular // ?: file irregolare
// Maschera per i bit di tipo. Per i file regolari, non viene impostato nulla.
ModeType = fs.ModeType
ModePerm = fs.ModePerm // Bit di permesso Unix, 0o777
)Di seguito è riportato un esempio di codice che apre un file in modalità lettura-scrittura, con permessi 0666, indicando che tutti possono leggere e scrivere il file, e verrà creato automaticamente se non esiste:
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
if os.IsNotExist(err) {
fmt.Println("Il file non esiste")
} else if err != nil {
fmt.Println("Accesso al file anomalo")
} else {
fmt.Println("Apertura del file riuscita", file.Name())
file.Close()
}
}Output:
Apertura del file riuscita README.txtSe si desidera solo ottenere alcune informazioni sul file senza leggerlo, è possibile utilizzare la funzione os.Stat() per l'operazione. Ecco un esempio di codice:
func main() {
fileInfo, err := os.Stat("README.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(fmt.Sprintf("%+v", fileInfo))
}
}Output:
&{name:README.txt FileAttributes:32 CreationTime:{LowDateTime:3603459389 HighDateTime:31016791} LastAccessTime:{LowDateTime:3603459389 HighDateTime:31016791} LastWriteTime:{LowDateTime:3603459389 HighDateTime:31016791} FileSizeHigh
:0 FileSizeLow:0 Reserved0:0 filetype:0 Mutex:{state:0 sema:0} path:README.txt vol:0 idxhi:0 idxlo:0 appendNameToPath:false}WARNING
Dopo aver aperto un file, ricordarsi sempre di chiuderlo. Generalmente l'operazione di chiusura viene inserita in un'istruzione defer:
defer file.Close()Lettura
Dopo aver aperto correttamente il file, è possibile eseguire operazioni di lettura. Per quanto riguarda le operazioni di lettura file, il tipo *os.File fornisce i seguenti metodi pubblici:
// Legge il file nella slice di byte passata
func (f *File) Read(b []byte) (n int, err error)
// Rispetto al primo, può leggere da un offset specificato
func (f *File) ReadAt(b []byte, off int64) (n int, err error)Nella maggior parte dei casi, il primo è più utilizzato. Per il primo metodo, è necessario scrivere manualmente la logica per l'espansione dinamica della slice durante la lettura. Ecco il codice:
func ReadFile(file *os.File) ([]byte, error) {
buffer := make([]byte, 0, 512)
for {
// Quando la capacità è insufficiente
if len(buffer) == cap(buffer) {
// Espansione
buffer = append(buffer, 0)[:len(buffer)]
}
// Continua a leggere il file
offset, err := file.Read(buffer[len(buffer):cap(buffer)])
// Include i dati scritti nella slice
buffer = buffer[:len(buffer)+offset]
// Quando si verifica un errore
if err != nil {
if errors.Is(err, io.EOF) {
err = nil
}
return buffer, err
}
}
}La logica rimanente è la seguente:
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
fmt.Println("Accesso al file anomalo")
} else {
fmt.Println("Apertura del file riuscita", file.Name())
bytes, err := ReadFile(file)
if err != nil {
fmt.Println("Lettura del file anomala", err)
} else {
fmt.Println(string(bytes))
}
file.Close()
}
}Output:
hello world!Oltre a ciò, è possibile utilizzare due funzioni convenienti per leggere i file, rispettivamente la funzione ReadFile del pacchetto os e la funzione ReadAll del pacchetto io. Per os.ReadFile, basta fornire il percorso del file. Per io.ReadAll, è necessario fornire un'implementazione del tipo io.Reader:
os.ReadFile
func ReadFile(name string) ([]byte, error)Esempio di utilizzo:
func main() {
bytes, err := os.ReadFile("README.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(bytes))
}
}Output:
hello world!io.ReadAll
func ReadAll(r Reader) ([]byte, error)Esempio di utilizzo:
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
fmt.Println("Accesso al file anomalo")
} else {
fmt.Println("Apertura del file riuscita", file.Name())
bytes, err := io.ReadAll(file)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(bytes))
}
file.Close()
}
}Apertura del file riuscita README.txt
hello world!Scrittura
La struttura os.File fornisce i seguenti metodi per scrivere dati:
// Scrive una slice di byte
func (f *File) Write(b []byte) (n int, err error)
// Scrive una stringa
func (f *File) WriteString(s string) (n int, err error)
// Scrive da una posizione specificata. Quando aperto in modalità os.O_APPEND, restituirà un errore
func (f *File) WriteAt(b []byte, off int64) (n int, err error)Se si desidera scrivere dati in un file, è necessario aprirlo in modalità O_WRONLY o O_RDWR, altrimenti non sarà possibile scrivere correttamente nel file. Di seguito è riportato un esempio che apre un file in modalità os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, con permessi 0666, e scrive dati specificati:
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666)
if err != nil {
fmt.Println("Accesso al file anomalo")
} else {
fmt.Println("Apertura del file riuscita", file.Name())
for i := 0; i < 5; i++ {
offset, err := file.WriteString("hello world!\n")
if err != nil {
fmt.Println(offset, err)
}
}
fmt.Println(file.Close())
}
}Poiché il file è aperto in modalità os.O_APPEND, i dati verranno aggiunti alla fine del file durante la scrittura. Al termine dell'esecuzione, il contenuto del file è il seguente:
hello world!
hello world!
hello world!
hello world!
hello world!Scrivere slice di byte in un file è un'operazione simile, non sarà ripetuta. Per le operazioni di scrittura su file, la libreria standard fornisce anche funzioni convenienti, rispettivamente os.WriteFile e io.WriteString:
os.WriteFile
func WriteFile(name string, data []byte, perm FileMode) errorEsempio di utilizzo:
func main() {
err := os.WriteFile("README.txt", []byte("hello world!\n"), 0666)
if err != nil {
fmt.Println(err)
}
}Il contenuto del file è il seguente:
hello world!io.WriteString
func WriteString(w Writer, s string) (n int, err error)Esempio di utilizzo:
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666)
if err != nil {
fmt.Println("Accesso al file anomalo")
} else {
fmt.Println("Apertura del file riuscita", file.Name())
for i := 0; i < 5; i++ {
offset, err := io.WriteString(file, "hello world!\n")
if err != nil {
fmt.Println(offset, err)
}
}
fmt.Println(file.Close())
}
}hello world!
hello world!
hello world!
hello world!
hello world!La funzione os.Create viene utilizzata per creare file ed è essenzialmente anche un wrapper di OpenFile:
func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}WARNING
Quando si crea un file, se la directory padre non esiste, la creazione fallirà e verrà restituito un errore.
Copia
Per quanto riguarda la copia di file, è necessario aprire due file contemporaneamente. Il primo metodo consiste nel leggere i dati dal file originale e poi scriverli nel file di destinazione. Ecco un esempio di codice:
func main() {
// Legge i dati dal file originale
data, err := os.ReadFile("README.txt")
if err != nil {
fmt.Println(err)
return
}
// Scrive nel file di destinazione
err = os.WriteFile("README(1).txt", data, 0666)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Copia riuscita")
}
}*os.File.ReadFrom
Un altro metodo consiste nell'utilizzare il metodo ReadFrom fornito da os.File. Quando si apre il file, uno in sola lettura e uno in sola scrittura:
func (f *File) ReadFrom(r io.Reader) (n int64, err error)Esempio di utilizzo:
func main() {
// Apre il file originale in sola lettura
origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
if err != nil {
fmt.Println(err)
return
}
defer origin.Close()
// Apre il file copia in sola scrittura
target, err := os.OpenFile("README(1).txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
fmt.Println(err)
return
}
defer target.Close()
// Legge i dati dal file originale e li scrive nel file copia
offset, err := target.ReadFrom(origin)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Copia del file riuscita", offset)
}Questo metodo di copia richiede prima di leggere l'intero contenuto del file sorgente in memoria, quindi di scriverlo nel file di destinazione. Non è consigliabile farlo quando il file è particolarmente grande.
io.Copy
Un altro metodo consiste nell'utilizzare la funzione io.Copy, che legge e scrive contemporaneamente, leggendo prima il contenuto in un buffer, quindi scrivendolo nel file di destinazione. La dimensione predefinita del buffer è 32KB:
func Copy(dst Writer, src Reader) (written int64, err error)Esempio di utilizzo:
func main() {
// Apre il file originale in sola lettura
origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
if err != nil {
fmt.Println(err)
return
}
defer origin.Close()
// Apre il file copia in sola scrittura
target, err := os.OpenFile("README(1).txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
fmt.Println(err)
return
}
defer target.Close()
// Copia
written, err := io.Copy(target, origin)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(written)
}
}È anche possibile utilizzare io.CopyBuffer per specificare la dimensione del buffer.
Rinomina
La rinomina può anche essere intesa come spostamento di file. Verrà utilizzata la funzione Rename del pacchetto os:
func Rename(oldpath, newpath string) errorEsempio:
func main() {
err := os.Rename("README.txt", "readme.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Rinomina riuscita")
}
}Questa funzione ha lo stesso effetto anche per le cartelle.
Eliminazione
L'operazione di eliminazione è molto più semplice rispetto ad altre operazioni. Verranno utilizzate solo due funzioni del pacchetto os:
// Elimina un singolo file o una directory vuota. Restituirà un errore quando la directory non è vuota
func Remove(name string) error
// Elimina tutti i file e le directory nella directory specificata, incluse sottodirectory e file secondari
func RemoveAll(path string) errorL'utilizzo è molto semplice. Di seguito è riportato un esempio di eliminazione di una directory:
func main() {
// Elimina tutti i file e le sottodirectory nella directory corrente
err := os.RemoveAll(".")
if err != nil {
fmt.Println(err)
}else {
fmt.Println("Eliminazione riuscita")
}
}Di seguito è riportato un esempio di eliminazione di un singolo file:
func main() {
// Elimina tutti i file e le sottodirectory nella directory corrente
err := os.Remove("README.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Eliminazione riuscita")
}
}Flush
La funzione os.Sync incapsula la chiamata di sistema sottostante Fsync, utilizzata per scrivere le IO memorizzate nella cache del sistema operativo sul disco:
func main() {
create, err := os.Create("test.txt")
if err != nil {
panic(err)
}
defer create.Close()
_, err = create.Write([]byte("hello"))
if err != nil {
panic(err)
}
// Flush su disco
if err := create.Sync();err != nil {
return
}
}Cartelle
Molte operazioni sulle cartelle sono simili alle operazioni sui file.
Lettura
Per le cartelle, ci sono due modi per aprirle:
os.ReadDir
Il primo modo è utilizzare la funzione os.ReadDir:
func ReadDir(name string) ([]DirEntry, error)func main() {
// Directory corrente
dir, err := os.ReadDir(".")
if err != nil {
fmt.Println(err)
} else {
for _, entry := range dir {
fmt.Println(entry.Name())
}
}
}*os.File.ReadDir
Il secondo modo è utilizzare la funzione *os.File.ReadDir. La funzione os.ReadDir è essenzialmente un semplice wrapper di *os.File.ReadDir:
// Quando n < 0, legge tutti i contenuti della cartella
func (f *File) ReadDir(n int) ([]DirEntry, error)func main() {
// Directory corrente
dir, err := os.Open(".")
if err != nil {
fmt.Println(err)
}
defer dir.Close()
dirs, err := dir.ReadDir(-1)
if err != nil {
fmt.Println(err)
} else {
for _, entry := range dirs {
fmt.Println(entry.Name())
}
}
}Creazione
L'operazione di creazione di una cartella utilizza due funzioni del pacchetto os:
// Crea una directory con il nome specificato e i permessi specificati
func Mkdir(name string, perm FileMode) error
// Rispetto alla prima, questa funzione creerà tutte le directory padre necessarie
func MkdirAll(path string, perm FileMode) errorEsempio:
func main() {
err := os.Mkdir("src", 0666)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Creazione riuscita")
}
}Copia
Possiamo scrivere manualmente una funzione per attraversare ricorsivamente l'intera cartella, ma la libreria standard filepath ha già fornito una funzione con funzionalità simile, quindi può essere utilizzata direttamente. Ecco un semplice esempio di codice per copiare una cartella:
func CopyDir(src, dst string) error {
// Controlla lo stato della cartella sorgente
_, err := os.Stat(src)
if err != nil {
return err
}
return filepath.Walk(src, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
// Calcola il percorso relativo
rel, err := filepath.Rel(src, path)
if err != nil {
return err
}
// Concatena il percorso di destinazione
destpath := filepath.Join(dst, rel)
// Crea la cartella
var dirpath string
var mode os.FileMode = 0755
if info.IsDir() {
dirpath = destpath
mode = info.Mode()
} else if info.Mode().IsRegular() {
dirpath = filepath.Dir(destpath)
}
if err := os.MkdirAll(dirpath, mode); err != nil {
return err
}
// Crea il file
if info.Mode().IsRegular() {
srcfile, err := os.Open(path)
if err != nil {
return err
}
// Ricordati di chiudere il file
defer srcfile.Close()
destfile, err := os.OpenFile(destpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode())
if err != nil {
return err
}
defer destfile.Close()
// Copia il contenuto del file
if _, err := io.Copy(destfile, srcfile); err != nil {
return err
}
return nil
}
return nil
})
}filepath.Walk attraversa ricorsivamente l'intera cartella. Durante il processo, crea cartelle quando incontra cartelle, crea nuovi file e copia quando incontra file. Il codice è un po' più lungo rispetto alla copia di file, ma non è complesso.
