Eine ganze Datei lesen und schreiben
os.WriteFile und os.ReadFile decken die meisten Fälle ab. Sie öffnen, schreiben oder lesen und schließen in einem Aufruf.
Die Beispiele auf dieser Seite arbeiten in einem temporären Verzeichnis aus os.MkdirTemp und entfernen es mit defer os.RemoveAll(dir), hinterlassen also nichts. In deinem eigenen Code wird ein relativer Pfad wie "config.json" gegen das Arbeitsverzeichnis des Prozesses aufgelöst, und das ist nicht unbedingt das Verzeichnis der Quelldatei oder des Binarys.
os.WriteFile legt die Datei bei Bedarf an und kürzt sie, wenn sie existiert. Das dritte Argument ist die Unix-Berechtigung für eine neu angelegte Datei: 0o644 bedeutet, dass der Besitzer lesen und schreiben und alle anderen lesen dürfen. Bei einer bereits existierenden Datei wird es ignoriert, und die umask des Prozesses kann Bits entfernen.
os.ReadFile liest alles in den Speicher. Für Konfigurationsdateien und kleine Eingaben ist das richtig, für ein Log mit mehreren Gigabyte falsch.
Zeilenweise lesen mit bufio.Scanner
Für große Dateien, oder wenn du ohnehin Zeilen willst, nimm bufio.Scanner. Er liest in Blöcken und gibt dir jeweils eine Zeile ohne Zeilenumbruch.
Drei Details:
- Prüf nach der Schleife
sc.Err().Scangibt sowohl am Dateiende als auch bei einem Fehlerfalsezurück, und nurErrunterscheidet die beiden. - Das Zeilenlimit von 64 KB. Standardmäßig stoppt eine einzelne Zeile über 64 KB den Scanner mit
bufio.Scanner: token too long. Bei Dateien mit langen Zeilen (minifiziertes JSON, manche Logs) hebst du das Limit vor der Schleife an:sc.Buffer(make([]byte, 1024*1024), 10*1024*1024). - Andere Einheiten.
sc.Split(bufio.ScanWords)liefert Wörter;bufio.ScanRunesliefert Zeichen.
Um einen Stream in Blöcken fester Größe statt in Zeilen zu lesen, nimm f.Read(buf) in einer Schleife oder io.Copy in einen anderen Writer.
Schreiben: os.Create, os.OpenFile und Anhängen
os.Create(name) öffnet eine Datei zum Schreiben und legt sie an oder kürzt sie. os.OpenFile gibt dir über Flags volle Kontrolle:
| Flag | Bedeutung |
|---|---|
os.O_RDONLY, os.O_WRONLY, os.O_RDWR | zum Lesen, Schreiben oder beidem öffnen (eins davon wählen) |
os.O_CREATE | die Datei anlegen, wenn sie nicht existiert |
os.O_TRUNC | die Datei beim Öffnen leeren |
os.O_APPEND | jeder Schreibzugriff landet am Ende |
os.O_EXCL | mit O_CREATE: fehlschlagen, wenn die Datei schon existiert |
os.Open(name) ist OpenFile(name, O_RDONLY, 0). os.Create(name) ist OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0o666).
Beim Schreiben zählt der Fehler von Close. Manche Dateisysteme melden Schreibfehler erst beim Schließen, also kann ein alleiniges defer f.Close() verlorene Daten verbergen. Bei Dateien, die du schreibst, prüfst du Close explizit, so wie appendLine es tut. Bei Dateien, die du nur liest, ist defer f.Close() in Ordnung.
fmt.Fprintln und die übrigen Ausgabefunktionen von fmt akzeptieren jeden io.Writer, auch eine Datei. Ein bufio.Writer sammelt kleine Schreibzugriffe im Speicher. w.Flush() zu vergessen ist ein klassischer Bug: Das Programm endet normal, und die letzten paar Kilobyte erreichen die Datei nie.
Existiert die Datei?
Go hat kein os.Exists. Ruf os.Stat auf und untersuche den Fehler:
errors.Is(err, fs.ErrNotExist) ist das aktuelle Idiom. Es ersetzt das ältere os.IsNotExist(err), das gewrappte Fehler nicht durchschaut.
Vor dem Öffnen zu prüfen ist oft unnötig und anfällig für Race Conditions: Die Datei kann zwischen Prüfung und Öffnen auftauchen oder verschwinden. Meist öffnest du sie einfach und behandelst fs.ErrNotExist beim Öffnen. Um eine Datei nur anzulegen, wenn sie noch nicht existiert, nimm O_CREATE|O_EXCL, das Prüfung und Anlegen zu einem atomaren Schritt macht.
Verzeichnisse
| Aufgabe | Funktion |
|---|---|
| ein Verzeichnis anlegen | os.Mkdir(path, 0o755) |
| einen Pfad samt Elternverzeichnissen anlegen | os.MkdirAll(path, 0o755) |
| ein Verzeichnis auflisten | os.ReadDir(path) |
| einen Baum durchlaufen | filepath.WalkDir(root, fn) |
| eine Datei oder ein leeres Verzeichnis löschen | os.Remove(path) |
| einen Baum löschen | os.RemoveAll(path) |
| umbenennen oder verschieben | os.Rename(old, new) |
| temporäre Datei oder temporäres Verzeichnis | os.CreateTemp("", "prefix-*"), os.MkdirTemp("", "prefix") |
| Pfadteile verbinden | filepath.Join(a, b, c) |
Nimm path/filepath für Pfade im Dateisystem: Es verwendet das richtige Trennzeichen für das Betriebssystem (\ unter Windows). Das Paket path ist für Pfade mit Schrägstrichen gedacht, etwa URLs.
Go 1.24 hat außerdem os.Root (os.OpenRoot(dir)) eingeführt, das Dateien nur innerhalb eines Verzeichnisses öffnet und Pfade ablehnt, die per .. oder Symlinks daraus ausbrechen. Nutz es, wenn Dateinamen von Nutzern kommen.
Häufige Fehler
- Fehler nicht prüfen. Jeder dieser Aufrufe kann fehlschlagen. Eine Datei, die sich nicht öffnen ließ, ist
nil, und jedes spätereRead,WriteoderClosedarauf gibtinvalid argumentzurück. Das verbirgt die eigentliche Ursache (die Datei fehlte oder die Berechtigung wurde verweigert). sc.Err()nach einer Scan-Schleife vergessen. Ein Lesefehler sieht dann aus wie das Dateiende.Flushbei einembufio.Writervergessen. Das Ende der Datei fehlt.- Den Fehler von
Closenach dem Schreiben ignorieren. Schreibfehler können erst dort auftauchen. defer f.Close()in einer Schleife über viele Dateien. Die Dateien bleiben offen, bis die Funktion zurückkehrt, und dir können die File Descriptors ausgehen. Verschieb den Rumpf in eine Funktion, damit jede Datei pro Durchlauf geschlossen wird.- Berechtigungen dezimal geschrieben.
644ist nicht0o644. Go liest644als Dezimalzahl, also0o1204, und setzt seltsame Bits.
Häufig gestellte Fragen
Wie lese ich in Go eine ganze Datei in einen String?
data, err := os.ReadFile("notes.txt") gibt den Inhalt als []byte zurück; konvertiere mit string(data). Es öffnet, liest und schließt die Datei für dich. Nimm es für Dateien, die bequem in den Speicher passen; große Dateien liest du mit bufio.Scanner Zeile für Zeile.
Wie lese ich in Go eine Datei Zeile für Zeile?
Öffne die Datei mit os.Open, defer f.Close(), verpack sie in bufio.NewScanner(f), lauf mit for sc.Scan() { line := sc.Text() } darüber und prüf nach der Schleife sc.Err(). Zeilen über 64 KB lassen den Scanner mit token too long scheitern, außer du vergrößerst seinen Puffer mit sc.Buffer.
Wie hänge ich in Go etwas an eine Datei an?
Öffne sie mit os.OpenFile(name, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644), schreib und prüf den Fehler von Close. O_CREATE legt die Datei an, falls sie nicht existiert, und O_APPEND sorgt dafür, dass jeder Schreibzugriff am Ende landet.
Wie prüfe ich in Go, ob eine Datei existiert?
Ruf os.Stat(path) auf und prüf den Fehler mit errors.Is(err, fs.ErrNotExist). Ein nil-Fehler bedeutet, dass sie existiert. Jeder andere Fehler (etwa fehlende Berechtigung) bedeutet, dass du es nicht sagen kannst, also behandle ihn separat, statt ihn als „existiert nicht“ zu werten.