Skip to content

Dateien

Go bietet folgende Standardbibliotheken für die Dateiverarbeitung:

  • os-Bibliothek: verantwortlich für die konkrete Implementierung der Interaktion mit dem Betriebssystem-Dateisystem
  • io-Bibliothek: Abstraktionsschicht für Lese- und Schreib-IO
  • fs-Bibliothek: Abstraktionsschicht für Dateisysteme

Dieser Artikel erklärt, wie man mit Go grundlegende Dateiverarbeitung durchführt.

Öffnen

Die beiden gängigsten Methoden zum Öffnen von Dateien verwenden die zwei Funktionen aus dem os-Paket. Die Open-Funktion gibt einen Dateizeiger und einen Fehler zurück:

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

Die OpenFile-Funktion ermöglicht eine feineren Kontrolle. Die Open-Funktion ist lediglich eine einfache Kapselung der OpenFile-Funktion.

go
func OpenFile(name string, flag int, perm FileMode) (*File, error)

Zuerst die erste Verwendungsmethode: Einfach den entsprechenden Dateinamen angeben. Der Code lautet wie folgt:

go
func main() {
   file, err := os.Open("README.txt")
   fmt.Println(file, err)
}

Der Standardsuchpfad für Dateien ist der Pfad, in dem sich die go.mod-Datei des Projekts befindet. Da im Projekt keine Datei README.txt existiert, wird natürlich ein Fehler zurückgegeben.

<nil> open README.txt: The system cannot find the file specified.

Da es viele Arten von IO-Fehlern gibt, muss manuell überprüft werden, ob die Datei existiert. Auch dafür stellt das os-Paket praktische Funktionen bereit. Der geänderte Code lautet:

go
func main() {
  file, err := os.Open("README.txt")
  if os.IsNotExist(err) {
    fmt.Println("Datei existiert nicht")
  } else if err != nil {
    fmt.Println("Dateizugriffsfehler")
  } else {
    fmt.Println("Datei erfolgreich gelesen", file)
  }
}

Erneute Ausgabe:

Datei existiert nicht

Tatsächlich öffnet die erste Funktion die Datei nur im Nur-Lese-Modus, sie kann nicht geändert werden:

go
func Open(name string) (*File, error) {
  return OpenFile(name, O_RDONLY, 0)
}

Mit der OpenFile-Funktion können mehr Details gesteuert werden, wie z.B. das Ändern des Dateideskriptors und der Dateiberechtigungen. Für Dateideskriptoren stellt das os-Paket folgende Konstanten zur Verfügung:

go
const (
   // Nur Lesen, Nur Schreiben, Lesen und Schreiben - einer muss angegeben werden
   O_RDONLY int = syscall.O_RDONLY // Datei im Nur-Lese-Modus öffnen
   O_WRONLY int = syscall.O_WRONLY // Datei im Nur-Schreib-Modus öffnen
   O_RDWR   int = syscall.O_RDWR   // Datei im Lese-Schreib-Modus öffnen
   // Die übrigen Werte steuern das Verhalten
   O_APPEND int = syscall.O_APPEND // Beim Schreiben in die Datei Daten am Ende anhängen
   O_CREATE int = syscall.O_CREAT  // Datei erstellen, wenn sie nicht existiert
   O_EXCL   int = syscall.O_EXCL   // Zusammen mit O_CREATE verwenden, Datei darf nicht existieren
   O_SYNC   int = syscall.O_SYNC   // Datei mit synchronem IO öffnen
   O_TRUNC  int = syscall.O_TRUNC  // Beschreibbare Datei beim Öffnen abschneiden
)

Für Dateiberechtigungen werden folgende Konstanten bereitgestellt:

go
const (
   ModeDir        = fs.ModeDir        // d: Verzeichnis
   ModeAppend     = fs.ModeAppend     // a: Nur Anhängen
   ModeExclusive  = fs.ModeExclusive  // l: Exklusiv
   ModeTemporary  = fs.ModeTemporary  // T: Temporäre Datei
   ModeSymlink    = fs.ModeSymlink    // L: Symbolischer Link
   ModeDevice     = fs.ModeDevice     // D: Gerätedatei
   ModeNamedPipe  = fs.ModeNamedPipe  // p: Benannte Pipe (FIFO)
   ModeSocket     = fs.ModeSocket     // S: Unix-Domain-Socket
   ModeSetuid     = fs.ModeSetuid     // u: setuid
   ModeSetgid     = fs.ModeSetgid     // g: setgid
   ModeCharDevice = fs.ModeCharDevice // c: Unix-Zeichengerät, erfordert ModeDevice
   ModeSticky     = fs.ModeSticky     // t: Sticky-Bit
   ModeIrregular  = fs.ModeIrregular  // ?: Unregelmäßige Datei

   // Maske für Typ-Bits. Für reguläre Dateien wird nichts gesetzt.
   ModeType = fs.ModeType

   ModePerm = fs.ModePerm // Unix-Berechtigungsbits, 0o777
)

Hier ist ein Codebeispiel, das eine Datei im Lese-Schreib-Modus öffnet, mit der Berechtigung 0666 (alle können die Datei lesen und schreiben), und sie automatisch erstellt, wenn sie nicht existiert:

go
func main() {
  file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
  if os.IsNotExist(err) {
    fmt.Println("Datei existiert nicht")
  } else if err != nil {
    fmt.Println("Dateizugriffsfehler")
  } else {
    fmt.Println("Datei erfolgreich geöffnet", file.Name())
    file.Close()
  }
}

Ausgabe:

Datei erfolgreich geöffnet README.txt

Wenn Sie nur Informationen über die Datei erhalten möchten, ohne sie tatsächlich zu lesen, können Sie die Funktion os.Stat() verwenden. Ein Codebeispiel:

go
func main() {
  fileInfo, err := os.Stat("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(fmt.Sprintf("%+v", fileInfo))
  }
}

Ausgabe:

&{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

Nach dem Öffnen einer Datei sollte diese immer geschlossen werden. Normalerweise wird der Schließvorgang in einer defer-Anweisung platziert:

go
defer file.Close()

Lesen

Nach dem erfolgreichen Öffnen einer Datei können Leseoperationen durchgeführt werden. Der Typ *os.File bietet folgende öffentliche Methoden für Leseoperationen:

go
// Liest die Datei in das übergebene Byte-Slice
func (f *File) Read(b []byte) (n int, err error)

// Im Vergleich zur ersten Methode kann von einem angegebenen Offset gelesen werden
func (f *File) ReadAt(b []byte, off int64) (n int, err error)

In den meisten Fällen wird die erste Methode häufiger verwendet. Für diese Methode muss die Logik für die dynamische Erweiterung des Slices beim Lesen selbst geschrieben werden. Der Code lautet:

go
func ReadFile(file *os.File) ([]byte, error) {
  buffer := make([]byte, 0, 512)
  for {
    // Wenn die Kapazität nicht ausreicht
    if len(buffer) == cap(buffer) {
      // Erweitern
      buffer = append(buffer, 0)[:len(buffer)]
    }
    // Datei weiterlesen
    offset, err := file.Read(buffer[len(buffer):cap(buffer)])
    // Geschriebene Daten zum Slice hinzufügen
    buffer = buffer[:len(buffer)+offset]
    // Bei Fehler
    if err != nil {
      if errors.Is(err, io.EOF) {
        err = nil
      }
      return buffer, err
    }
  }
}

Die übrige Logik lautet:

go
func main() {
   file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
   if err != nil {
      fmt.Println("Dateizugriffsfehler")
   } else {
      fmt.Println("Datei erfolgreich geöffnet", file.Name())
      bytes, err := ReadFile(file)
      if err != nil {
         fmt.Println("Dateilesefehler", err)
      } else {
         fmt.Println(string(bytes))
      }
      file.Close()
   }
}

Ausgabe:

Datei erfolgreich geöffnet README.txt
hello world!

Alternativ können zwei Hilfsfunktionen zum Lesen von Dateien verwendet werden: os.ReadFile aus dem os-Paket und io.ReadAll aus dem io-Paket. Für os.ReadFile muss lediglich der Dateipfad angegeben werden, während für io.ReadAll eine Implementierung vom Typ io.Reader benötigt wird.

os.ReadFile

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

Verwendungsbeispiel:

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

Ausgabe:

hello world!

io.ReadAll

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

Verwendungsbeispiel:

go
func main() {

   file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
   if err != nil {
      fmt.Println("Dateizugriffsfehler")
   } else {
      fmt.Println("Datei erfolgreich geöffnet", file.Name())
      bytes, err := io.ReadAll(file)
      if err != nil {
         fmt.Println(err)
      } else {
         fmt.Println(string(bytes))
      }
      file.Close()
   }
}
Datei erfolgreich geöffnet README.txt
hello world!

Schreiben

Die Struktur os.File bietet mehrere Methoden zum Schreiben von Daten:

go
// Byte-Slice schreiben
func (f *File) Write(b []byte) (n int, err error)

// String schreiben
func (f *File) WriteString(s string) (n int, err error)

// Ab einer bestimmten Position schreiben, gibt einen Fehler zurück, wenn mit os.O_APPEND-Modus geöffnet
func (f *File) WriteAt(b []byte, off int64) (n int, err error)

Um Daten in eine Datei zu schreiben, muss die Datei im Modus O_WRONLY oder O_RDWR geöffnet werden, andernfalls kann nicht erfolgreich geschrieben werden. Hier ist ein Beispiel, das eine Datei im Modus os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC öffnet und mit der Berechtigung 0666 Daten in die angegebene Datei schreibt:

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("Dateizugriffsfehler")
  } else {
    fmt.Println("Datei erfolgreich geöffnet", 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())
  }
}

Da die Datei im Modus os.O_APPEND geöffnet wurde, werden die Daten beim Schreiben an das Ende der Datei angehängt. Nach der Ausführung lautet der Dateiinhalt:

txt
hello world!
hello world!
hello world!
hello world!
hello world!

Das Schreiben eines Byte-Slices in eine Datei ist ein ähnlicher Vorgang und wird hier nicht weiter erläutert. Für Schreiboperationen in Dateien bietet die Standardbibliothek ebenfalls Hilfsfunktionen: os.WriteFile und io.WriteString.

os.WriteFile

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

Verwendungsbeispiel:

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

Der Dateiinhalt lautet nun:

txt
hello world!

io.WriteString

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

Verwendungsbeispiel:

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("Dateizugriffsfehler")
   } else {
      fmt.Println("Datei erfolgreich geöffnet", 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!

Die Funktion os.Create dient zum Erstellen von Dateien und ist im Wesentlichen eine Kapselung von OpenFile:

go
func Create(name string) (*File, error) {
   return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

WARNING

Beim Erstellen einer Datei schlägt der Vorgang fehl und gibt einen Fehler zurück, wenn das übergeordnete Verzeichnis nicht existiert.

Kopieren

Für das Kopieren von Dateien müssen zwei Dateien gleichzeitig geöffnet werden. Die erste Methode besteht darin, die Daten aus der Originaldatei zu lesen und dann in die Zieldatei zu schreiben. Ein Codebeispiel:

go
func main() {
    // Daten aus der Originaldatei lesen
  data, err := os.ReadFile("README.txt")
  if err != nil {
    fmt.Println(err)
    return
  }
    // In die Zieldatei schreiben
  err = os.WriteFile("README(1).txt", data, 0666)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Kopieren erfolgreich")
  }
}

*os.File.ReadFrom

Eine andere Methode verwendet die ReadFrom-Methode des os.File. Beim Öffnen der Dateien wird eine nur lesbar und die andere nur schreibbar geöffnet.

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

Verwendungsbeispiel:

go
func main() {
  // Originaldatei im Nur-Lese-Modus öffnen
  origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer origin.Close()
  // Kopiedatei im Nur-Schreib-Modus öffnen
  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()
  // Daten aus der Originaldatei lesen und in die Kopiedatei schreiben
  offset, err := target.ReadFrom(origin)
  if err != nil {
    fmt.Println(err)
    return
  }
  fmt.Println("Datei erfolgreich kopiert", offset)
}

Diese Kopiermethode erfordert, dass der gesamte Inhalt der Quelldatei zuerst in den Speicher gelesen wird, bevor er in die Zieldatei geschrieben wird. Dies wird nicht empfohlen, wenn die Datei besonders groß ist.

io.Copy

Eine andere Methode ist die Verwendung der Funktion io.Copy. Sie liest und schreibt gleichzeitig, liest zuerst den Inhalt in einen Puffer und schreibt dann in die Zieldatei. Die Standard-Puffergröße beträgt 32 KB.

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

Verwendungsbeispiel:

go
func main() {
  // Originaldatei im Nur-Lese-Modus öffnen
  origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer origin.Close()
  // Kopiedatei im Nur-Schreib-Modus öffnen
  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()
  // Kopieren
  written, err := io.Copy(target, origin)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(written)
  }
}

Sie können auch io.CopyBuffer verwenden, um die Puffergröße anzugeben.

Umbenennen

Umbenennen kann auch als Verschieben von Dateien verstanden werden. Dazu wird die Funktion Rename aus dem os-Paket verwendet.

go
func Rename(oldpath, newpath string) error

Beispiel:

go
func main() {
  err := os.Rename("README.txt", "readme.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Umbenennen erfolgreich")
  }
}

Diese Funktion funktioniert auch für Ordner.

Löschen

Löschoperationen sind einfacher als andere Operationen. Es werden nur zwei Funktionen aus dem os-Paket verwendet:

go
// Löscht eine einzelne Datei oder ein leeres Verzeichnis. Gibt einen Fehler zurück, wenn das Verzeichnis nicht leer ist
func Remove(name string) error

// Löscht alle Dateien und Verzeichnisse im angegebenen Verzeichnis, einschließlich Unterverzeichnisse und Unterdateien
func RemoveAll(path string) error

Die Verwendung ist sehr einfach. Hier ist ein Beispiel zum Löschen eines Verzeichnisses:

go
func main() {
  // Alle Dateien und Unterverzeichnisse im aktuellen Verzeichnis löschen
  err := os.RemoveAll(".")
  if err != nil {
    fmt.Println(err)
  }else {
    fmt.Println("Löschen erfolgreich")
  }
}

Hier ist ein Beispiel zum Löschen einer einzelnen Datei:

go
func main() {
  // Eine einzelne Datei im aktuellen Verzeichnis löschen
  err := os.Remove("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Löschen erfolgreich")
  }
}

Synchronisieren

Die Funktion os.Sync kapselt den Systemaufruf Fsync und dient dazu, die im Betriebssystem zwischengespeicherten IO-Schreibvorgänge tatsächlich auf die Festplatte zu schreiben:

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)
  }

    // Auf Festplatte schreiben
  if err := create.Sync();err != nil {
    return
  }
}

Ordner

Viele Operationen für Ordner ähneln den Operationen für Dateien.

Lesen

Für Ordner gibt es zwei Methoden zum Öffnen:

os.ReadDir

Die erste Methode verwendet die Funktion os.ReadDir:

go
func ReadDir(name string) ([]DirEntry, error)
go
func main() {
   // Aktuelles Verzeichnis
   dir, err := os.ReadDir(".")
   if err != nil {
      fmt.Println(err)
   } else {
      for _, entry := range dir {
         fmt.Println(entry.Name())
      }
   }
}

*os.File.ReadDir

Die zweite Methode verwendet die Funktion *os.File.ReadDir. os.ReadDir ist im Wesentlichen nur eine einfache Kapselung von *os.File.ReadDir.

go
// Wenn n < 0, wird der gesamte Inhalt des Ordners gelesen
func (f *File) ReadDir(n int) ([]DirEntry, error)
go
func main() {
   // Aktuelles Verzeichnis
   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())
      }
   }
}

Erstellen

Zum Erstellen von Ordnern werden zwei Funktionen aus dem os-Paket verwendet:

go
// Erstellt ein Verzeichnis mit dem angegebenen Namen und den angegebenen Berechtigungen
func Mkdir(name string, perm FileMode) error

// Im Vergleich zur ersten Funktion erstellt diese Funktion alle notwendigen übergeordneten Verzeichnisse
func MkdirAll(path string, perm FileMode) error

Beispiel:

go
func main() {
  err := os.Mkdir("src", 0666)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Erstellen erfolgreich")
  }
}

Kopieren

Wir können selbst eine Funktion schreiben, die rekursiv den gesamten Ordner durchläuft. Die Standardbibliothek filepath bietet jedoch bereits eine ähnliche Funktion, die direkt verwendet werden kann. Ein einfaches Codebeispiel zum Kopieren eines Ordners:

go
func CopyDir(src, dst string) error {
    // Status des Quellordners überprüfen
  _, 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
    }

        // Relativen Pfad berechnen
    rel, err := filepath.Rel(src, path)
    if err != nil {
      return err
    }

        // Zielpfad zusammenstellen
    destpath := filepath.Join(dst, rel)

        // Ordner erstellen
    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
    }

        // Datei erstellen
    if info.Mode().IsRegular() {
      srcfile, err := os.Open(path)
      if err != nil {
        return err
      }
            // Datei unbedingt schließen
      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()

            // Dateiinhalt kopieren
      if _, err := io.Copy(destfile, srcfile); err != nil {
        return err
      }
      return nil
    }

    return nil
  })
}

filepath.Walk durchläuft rekursiv den gesamten Ordner. Währenddessen wird beim Auftreten eines Ordners ein neuer Ordner erstellt, und beim Auftreten einer Datei wird eine neue Datei erstellt und kopiert. Der Code ist etwas umfangreicher als beim Kopieren von Dateien, aber nicht besonders komplex.

Golang by www.golangdev.cn edit