1. Podstawy biblioteki OS

Pakiet os w Golang zapewnia platformowo niezależny interfejs do funkcji systemu operacyjnego. Następnie omówimy, jak korzystać z pakietu os do obsługi otwierania, zamykania, czytania, zapisywania plików, a także uzyskiwania i ustawiania atrybutów plików.

1.1 Otwieranie i Zamykanie Plików

W języku Go można użyć funkcji os.Open, aby otworzyć plik, która zwróci obiekt *os.File oraz błąd. Po otwarciu pliku można wykonywać operacje odczytu, zapisu i inne. Po zakończeniu operacji należy wywołać file.Close, aby zamknąć plik i zwolnić odpowiadające zasoby.

Poniżej znajduje się przykład otwarcia pliku:

package main

import (
    "fmt"
    "os"
)

func main() {
    // Otwórz plik test.txt w bieżącym katalogu
    file, err := os.Open("test.txt")
    if err != nil {
        // Obsłuż błąd otwierania pliku
        fmt.Println("Błąd podczas otwierania pliku:", err)
        return
    }
    // Użyj instrukcji defer, aby zapewnić, że plik zostanie ostatecznie zamknięty
    defer file.Close()

    // Operacje na pliku...

    fmt.Println("Plik został pomyślnie otwarty")
}

W powyższym kodzie używamy instrukcji defer, aby zapewnić, że file.Close zostanie wykonane niezależnie od tego, co się stanie. To jest powszechna praktyka w języku Go do czyszczenia zasobów.

1.2 Operacje Odczytu i Zapisu Pliku

Typ os.File posiada metody Read i Write, które można użyć do operacji odczytu i zapisu pliku. Metoda Read odczytuje dane z pliku do wycinka bajtów, a metoda Write zapisuje dane z wycinka bajtów do pliku.

Poniższy przykład demonstruje, jak czytać i zapisywać do pliku:

package main

import (
    "fmt"
    "os"
)

func main() {
    // Otwórz plik
    file, err := os.OpenFile("test.txt", os.O_RDWR, 0644)
    if err != nil {
        fmt.Println("Błąd podczas otwierania pliku:", err)
        return
    }
    defer file.Close()

    // Zapisz zawartość pliku
    message := []byte("Witaj, Gopherzy!")
    _, writeErr := file.Write(message)
    if writeErr != nil {
        fmt.Println("Błąd podczas zapisywania do pliku:", writeErr)
        return
    }

    // Odczytaj plik od początku
    file.Seek(0, 0)
    buffer := make([]byte, len(message))
    _, readErr := file.Read(buffer)
    if readErr != nil {
        fmt.Println("Błąd odczytu pliku:", readErr)
        return
    }

    fmt.Println("Zawartość pliku:", string(buffer))
}

W tym przykładzie używamy os.OpenFile zamiast os.Open. Funkcja os.OpenFile pozwala określić tryb i uprawnienia do użycia podczas otwierania pliku. W powyższym przykładzie użyto flagi os.O_RDWR, co oznacza, że plik zostanie otwarty w trybie odczytu i zapisu.

1.3 Właściwości plików i uprawnienia

Możesz użyć funkcji z pakietu os, aby uzyskać dostęp do informacji o statusie pliku i je modyfikować. Użyj os.Stat lub os.Lstat, aby uzyskać interfejs os.FileInfo, który dostarcza informacje o pliku, takie jak rozmiar, uprawnienia, czas modyfikacji i inne.

Oto przykład, jak uzyskać status pliku:

package main

import (
    "fmt"
    "os"
)

func main() {
    fileInfo, err := os.Stat("test.txt")
    if err != nil {
        fmt.Println("Błąd podczas pobierania informacji o pliku:", err)
        return
    }

    // Wyświetl rozmiar pliku
    fmt.Printf("Rozmiar pliku: %d bajtów\n", fileInfo.Size())

    // Wyświetl uprawnienia pliku
    fmt.Printf("Uprawnienia pliku: %s\n", fileInfo.Mode())
}

Jeśli chcesz zmienić nazwę pliku lub modyfikować jego uprawnienia, możesz użyć os.Rename do zmiany nazwy pliku lub os.Chmod do zmiany uprawnień pliku.

package main

import (
    "fmt"
    "os"
)

func main() {
    // Zmień uprawnienia pliku na tylko do odczytu
    err := os.Chmod("test.txt", 0444)
    if err != nil {
        fmt.Println("Błąd podczas zmiany uprawnień pliku:", err)
        return
    }

    // Zmień nazwę pliku
    renameErr := os.Rename("test.txt", "zmieniona-nazwa.txt")
    if renameErr != nil {
        fmt.Println("Błąd podczas zmiany nazwy pliku:", renameErr)
        return
    }
    
    fmt.Println("Operacje na pliku zakończone pomyślnie")
}

Tutaj zmieniamy uprawnienia pliku test.txt na tylko do odczytu, a następnie zmieniamy nazwę pliku na zmieniona-nazwa.txt. Zauważ, że podczas modyfikacji uprawnień pliku należy zachować ostrożność, ponieważ niewłaściwe ustawienia uprawnień mogą prowadzić do niedostępnych plików.

2. Podstawowe użycie biblioteki IO

W języku Go biblioteka io dostarcza podstawowe interfejsy dla podstawowych operacji wejścia/wyjścia (I/O). Projektowanie biblioteki io kieruje się zasadami prostoty i jednolitymi interfejsami, zapewniając podstawowe wsparcie dla różnych rodzajów operacji wejścia/wyjścia, takich jak odczyt/zapis plików, komunikacja sieciowa, buforowanie danych i wiele więcej.

2.2 Korzystanie z interfejsów Reader i Writer

io.Reader i io.Writer to dwa podstawowe interfejsy używane do określenia operacji odczytu i zapisu obiektu. Są one implementowane przez różne typy, takie jak pliki, połączenia sieciowe i bufory.

io.Reader

Interfejs io.Reader posiada metodę Read:

Read(p []byte) (n int, err error)

Ta metoda czyta do len(p) bajtów danych z io.Reader do p. Zwraca liczbę odczytanych bajtów n (0 <= n <= len(p)) oraz napotkany błąd.

Przykładowy kod:

package main

import (
    "fmt"
    "io"
    "strings"
)

func main() {
    r := strings.NewReader("Witaj, świecie!")

    buf := make([]byte, 4)
    for {
        n, err := r.Read(buf)
        if err == io.EOF {
            break
        }
        fmt.Printf("Odczytane bajty: %d, Zawartość: %s\n", n, buf[:n])
    }
}

W tym przykładzie tworzymy strings.NewReader, aby odczytać dane z ciągu znaków, a następnie czytamy dane porcjami po 4 bajty.

io.Writer

Interfejs io.Writer posiada metodę Write:

Write(p []byte) (n int, err error)

Ta metoda zapisuje dane z p do strumienia danych podstawowego, zwracając liczbę zapisanych bajtów oraz napotkany błąd.

Przykładowy kod:

package main

import (
    "fmt"
    "os"
)

func main() {
    data := []byte("Witaj, świecie!\n")
    n, err := os.Stdout.Write(data)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Zapisane bajty: %d\n", n)
}

Ten przykład zapisuje prosty ciąg znaków do standardowego wyjścia os.Stdout, które działa jako implementacja io.Writer.

2.3 Zaawansowane funkcje odczytu/zapisu

Pakiet io udostępnia kilka zaawansowanych funkcji, które mogą uprościć powszechne zadania, takie jak kopiowanie danych i odczytywanie określonej ilości danych.

Funkcja Kopiowania

io.Copy to wygodna metoda bezpośredniego kopiowania danych z io.Reader do io.Writer bez potrzeby pośredniego buforowania.

Przykładowy kod:

package main

import (
    "io"
    "os"
    "strings"
)

func main() {
    r := strings.NewReader("Przykład prostego kopiowania")
    _, err := io.Copy(os.Stdout, r)
    if err != nil {
        panic(err)
    }
}

W tym przykładzie bezpośrednio kopiujemy ciąg znaków na standardowe wyjście.

Funkcja ReadAtLeast

Funkcja io.ReadAtLeast służy do zapewnienia, że z io.Reader zostanie odczytana co najmniej określona ilość danych przed zwróceniem wyniku.

func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)

Przykładowy kod:

package main

import (
    "fmt"
    "io"
    "strings"
)

func main() {
    r := strings.NewReader("Strona Chińskiego Języka Go")
    buf := make([]byte, 14)
    n, err := io.ReadAtLeast(r, buf, 14)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("%s\n", buf[:n])
}

W tym przykładzie io.ReadAtLeast próbuje odczytać co najmniej 14 bajtów danych do buf.

Te zaawansowane funkcje odczytu/zapisu pozwalają efektywniej obsługiwać zadania związane z wejściem/wyjściem i stanowią solidną podstawę do budowania bardziej złożonej logiki programu.