Odczyt i zapis całego pliku
os.WriteFile i os.ReadFile pokrywają większość potrzeb. W jednym wywołaniu otwierają plik, zapisują lub czytają i go zamykają.
Przykłady na tej stronie działają w katalogu tymczasowym z os.MkdirTemp i usuwają go przez defer os.RemoveAll(dir), więc nic po sobie nie zostawiają. We własnym kodzie ścieżka względna, taka jak "config.json", jest rozwiązywana względem katalogu roboczego procesu, który niekoniecznie jest katalogiem pliku źródłowego ani programu wykonywalnego.
os.WriteFile w razie potrzeby tworzy plik, a jeśli istnieje, obcina go. Trzeci argument to uprawnienia Unix dla nowo tworzonego pliku: 0o644 oznacza, że właściciel może czytać i zapisywać, a wszyscy inni mogą czytać. Dla pliku, który już istnieje, argument jest ignorowany, a umask procesu może usunąć część bitów.
os.ReadFile wczytuje wszystko do pamięci. To dobre rozwiązanie dla plików konfiguracyjnych i małych danych wejściowych, a złe dla wielogigabajtowego logu.
Czytanie linia po linii przez bufio.Scanner
Dla dużych plików, albo gdy i tak chcesz dostać linie, użyj bufio.Scanner. Czyta porcjami i podaje po jednej linii naraz, bez znaku nowej linii.
Trzy szczegóły:
- Po pętli sprawdź
sc.Err().Scanzwracafalsezarówno na końcu pliku, jak i przy błędzie, i tylkoErrpozwala je odróżnić. - Limit 64 KB na linię. Domyślnie pojedyncza linia dłuższa niż 64 KB zatrzymuje scanner z błędem
bufio.Scanner: token too long. Dla plików z długimi liniami (zminifikowany JSON, niektóre logi) podnieś limit przed pętlą:sc.Buffer(make([]byte, 1024*1024), 10*1024*1024). - Inne jednostki.
sc.Split(bufio.ScanWords)zwraca słowa;bufio.ScanRuneszwraca znaki.
Do czytania strumienia porcjami o stałym rozmiarze zamiast liniami użyj f.Read(buf) w pętli albo io.Copy do innego writera.
Zapis: os.Create, os.OpenFile i dopisywanie
os.Create(name) otwiera plik do zapisu, tworząc go lub obcinając. os.OpenFile daje pełną kontrolę przez flagi:
| Flaga | Znaczenie |
|---|---|
os.O_RDONLY, os.O_WRONLY, os.O_RDWR | otwarcie do odczytu, zapisu lub obu (wybierz jedno) |
os.O_CREATE | utwórz plik, jeśli nie istnieje |
os.O_TRUNC | wyczyść plik przy otwarciu |
os.O_APPEND | każdy zapis trafia na koniec |
os.O_EXCL | z O_CREATE: błąd, jeśli plik już istnieje |
os.Open(name) to OpenFile(name, O_RDONLY, 0). os.Create(name) to OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0o666).
Przy zapisie błąd z Close ma znaczenie. Niektóre systemy plików zgłaszają błędy zapisu dopiero przy zamykaniu, więc samo defer f.Close() może ukryć utracone dane. Dla plików, do których zapisujesz, sprawdzaj Close jawnie, tak jak robi to appendLine. Dla plików, które tylko czytasz, defer f.Close() wystarczy.
fmt.Fprintln i pozostałe funkcje wypisujące z fmt przyjmują dowolny io.Writer, w tym plik. bufio.Writer zbiera małe zapisy w pamięci. Zapomniane w.Flush() to klasyczny błąd: program kończy się normalnie, a ostatnie kilka kilobajtów nigdy nie trafia do pliku.
Czy plik istnieje?
Go nie ma os.Exists. Wywołaj os.Stat i sprawdź błąd:
errors.Is(err, fs.ErrNotExist) to obecny idiom. Zastępuje starsze os.IsNotExist(err), które nie widzi opakowanych błędów.
Sprawdzanie przed otwarciem często jest zbędne i podatne na wyścigi: plik może pojawić się lub zniknąć między sprawdzeniem a otwarciem. Zwykle po prostu otwierasz plik i obsługujesz fs.ErrNotExist z otwarcia. Żeby utworzyć plik tylko wtedy, gdy jeszcze nie istnieje, użyj O_CREATE|O_EXCL, co łączy sprawdzenie i utworzenie w jeden atomowy krok.
Katalogi
| Zadanie | Funkcja |
|---|---|
| utworzenie jednego katalogu | os.Mkdir(path, 0o755) |
| utworzenie ścieżki z katalogami nadrzędnymi | os.MkdirAll(path, 0o755) |
| wylistowanie katalogu | os.ReadDir(path) |
| przejście po drzewie | filepath.WalkDir(root, fn) |
| usunięcie pliku lub pustego katalogu | os.Remove(path) |
| usunięcie drzewa | os.RemoveAll(path) |
| zmiana nazwy lub przeniesienie | os.Rename(old, new) |
| plik lub katalog tymczasowy | os.CreateTemp("", "prefix-*"), os.MkdirTemp("", "prefix") |
| łączenie części ścieżki | filepath.Join(a, b, c) |
Do ścieżek w systemie plików używaj path/filepath: stosuje właściwy separator dla systemu operacyjnego (\ w Windows). Pakiet path jest do ścieżek rozdzielanych ukośnikiem, takich jak URL-e.
Go 1.24 dodało też os.Root (os.OpenRoot(dir)), które otwiera pliki tylko w obrębie jednego katalogu i odrzuca ścieżki, które z niego uciekają przez .. lub dowiązania symboliczne. Używaj go, gdy nazwy plików pochodzą od użytkowników.
Typowe błędy
- Brak sprawdzania błędów. Każde z tych wywołań może się nie powieść. Plik, którego nie udało się otworzyć, to
nil, a każde późniejszeRead,WriteczyClosena nim zwracainvalid argument, co ukrywa prawdziwą przyczynę (pliku nie było albo zabrakło uprawnień). - Zapomniane
sc.Err()po pętli skanującej. Błąd odczytu wygląda jak koniec pliku. - Zapomniane
Flushnabufio.Writer. Brakuje końca pliku. - Ignorowanie błędu z
Closepo zapisie. Błędy zapisu mogą ujawnić się tylko tam. defer f.Close()w pętli po wielu plikach. Pliki pozostają otwarte, dopóki funkcja nie zwróci wyniku, i mogą skończyć się deskryptory plików. Przenieś ciało pętli do funkcji, żeby każdy plik zamykał się w swojej iteracji.- Uprawnienia zapisane dziesiętnie.
644to nie0o644. Go czyta644jako liczbę dziesiętną, czyli0o1204, co ustawia dziwne bity.
Najczęściej zadawane pytania
Jak wczytać cały plik do stringa w Go?
data, err := os.ReadFile("notes.txt") zwraca zawartość jako []byte; skonwertuj ją przez string(data). Funkcja sama otwiera, czyta i zamyka plik. Używaj jej dla plików, które swobodnie mieszczą się w pamięci; duże pliki czytaj linia po linii przez bufio.Scanner.
Jak czytać plik linia po linii w Go?
Otwórz plik przez os.Open, dodaj defer f.Close(), opakuj go w bufio.NewScanner(f), iteruj pętlą for sc.Scan() { line := sc.Text() } i po pętli sprawdź sc.Err(). Linie dłuższe niż 64 KB sprawiają, że scanner kończy się błędem token too long, chyba że powiększysz jego bufor przez sc.Buffer.
Jak dopisać dane do pliku w Go?
Otwórz go przez os.OpenFile(name, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644), zapisz dane i sprawdź błąd z Close. O_CREATE tworzy plik, jeśli nie istnieje, a O_APPEND sprawia, że każdy zapis trafia na koniec.
Jak sprawdzić, czy plik istnieje w Go?
Wywołaj os.Stat(path) i sprawdź błąd przez errors.Is(err, fs.ErrNotExist). Błąd nil oznacza, że plik istnieje. Każdy inny błąd (na przykład brak uprawnień) oznacza, że nie da się tego stwierdzić, więc obsłuż go osobno, zamiast traktować jak "nie istnieje".