Skip to content

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:

go
func Open(name string) (*File, error)

La seconda funzione OpenFile offre un controllo più granulare. La funzione Open è un semplice wrapper della funzione OpenFile:

go
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:

go
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:

go
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 esiste

In effetti, la prima funzione legge solo file in sola lettura e non può essere modificata:

go
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:

go
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:

go
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:

go
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.txt

Se si desidera solo ottenere alcune informazioni sul file senza leggerlo, è possibile utilizzare la funzione os.Stat() per l'operazione. Ecco un esempio di codice:

go
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:

go
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:

go
// 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:

go
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:

go
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

go
func ReadFile(name string) ([]byte, error)

Esempio di utilizzo:

go
func main() {
  bytes, err := os.ReadFile("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(string(bytes))
  }
}

Output:

hello world!

io.ReadAll

go
func ReadAll(r Reader) ([]byte, error)

Esempio di utilizzo:

go
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:

go
// 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:

go
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:

txt
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

go
func WriteFile(name string, data []byte, perm FileMode) error

Esempio di utilizzo:

go
func main() {
  err := os.WriteFile("README.txt", []byte("hello world!\n"), 0666)
  if err != nil {
    fmt.Println(err)
  }
}

Il contenuto del file è il seguente:

txt
hello world!

io.WriteString

go
func WriteString(w Writer, s string) (n int, err error)

Esempio di utilizzo:

go
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:

go
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:

go
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:

go
func (f *File) ReadFrom(r io.Reader) (n int64, err error)

Esempio di utilizzo:

go
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:

go
func Copy(dst Writer, src Reader) (written int64, err error)

Esempio di utilizzo:

go
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:

go
func Rename(oldpath, newpath string) error

Esempio:

go
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:

go
// 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) error

L'utilizzo è molto semplice. Di seguito è riportato un esempio di eliminazione di una directory:

go
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:

go
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:

go
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:

go
func ReadDir(name string) ([]DirEntry, error)
go
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:

go
// Quando n < 0, legge tutti i contenuti della cartella
func (f *File) ReadDir(n int) ([]DirEntry, error)
go
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:

go
// 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) error

Esempio:

go
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:

go
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.

Golang by www.golangdev.cn edit