D/Obsługa plików

< D

Obsługa plikówEdytuj

Obsługa plików jest bardzo często wykorzystywana w programowaniu - to właśnie w plikach przechowywane są wszelkie informacje, które muszą zostać zachowane po wyłączeniu programu (konfiguracja, wyniki i inne).

W języku D do różnych operacji na plikach wykorzystujemy moduł std.file, choć w wypadku odczytu i zapisu do plików wygodniej jest posłużyć się strumieniami i modułem std.stream.

Sprawdzanie, czy plik istniejeEdytuj

Aby sprawdzić, czy dany plik istnieje, należy się posłużyć funkcją bool exists(char[] filename) zadeklarowaną w module std.file (pamiętaj, by go wcześniej zaimportować). Jako argument podajemy nazwę pliku. Jeśli podamy tylko nazwę pliku, system szuka plików w obecnym katalogu roboczym. Jeśli podamy pełną ścieżkę (ścieżka bezwzględna), system poszuka pliku we wskazanym miejscu.

Przykład użycia tej funkcji ilustruje poniższy kod:

 import std.stdio;
 import std.file;
 
 void main() {
   if (exists("plik.txt")) {
     writefln("Plik istnieje");
   } else {
     writefln("Plik nie istnieje");
   }
 }

Odczytywanie danych z plikuEdytuj

Istnieje wiele sposobów odczytu i zapisu danych z plików. Zajmiemy się tutaj funkcjami dostępnymi w module std.file.

Strumień służący do odczytu i zapisu plików reprezentowany jest przez klasę File (więcej o klasach, konstruktorach itd. w rozdziałach poświęconych obiektowości). Należy więc utworzyć jej obiekt:

auto plik = File("plik.txt", "r");

Pierwszy parametr konstruktora zawiera nazwę pliku, który chcemy otworzyć. Drugi, tryb. Tryb może być jedną z wymienionych wartości:

Tryb Opis
r Otwiera istniejący plik w celu odczytu.
w Otwiera plik do zapisu, jeśli plik nie istnieje utworzony zostanie nowy. Zapis rozpocznie się od początku pliku.
a Otwiera plik do zapisu, jeśli plik nie istnieje utworzony zostanie nowy. Nowe dane dopisywane są do końca pliku.
r+ Otwiera plik z uprawnieniami do odczytu i zapisu.
w+ Otwiera plik z uprawnieniami do odczytu i zapisu. Redukuje zawartość do zera jeśli plik istnieje, jeśli nie tworzony jest nowy.
a+ Otwiera plik z uprawnieniami do odczytu i zapisu. Jeśli plik nie istnieje tworzony jest nowy. Odczyt zaczyna się od początku pliku ale nowe dane dopisywane są na końcu

Aktualnie wykorzystujemy ten pierwszy. Przed otwarciem pliku do odczytu warto się upewnić, że ten plik istnieje. W przeciwnym razie podczas wykonania programu dostaniemy błąd, który może nie być zrozumiały dla osoby obsługującej błąd.

Jeżeli już uda się otworzyć plik, możemy zacząć odczytywać z niego dane. By czytać dane z pliku należy skorzystać z funkcji File.readf, o składni takiej jak readf (wobec konsoli) i zbliżonej do writefln.

 int a;
 plik.readf("%d", &a);
 //%d - liczba całkowita, & oznacza pobranie adresu w pamięci, jeśli nie przeczytałeś rozdziału o wskaźnikach możesz nie zaprzątać sobie tym głowy

Tekst można również wczytywać linia po linii. Do tego służy metoda File.readln(). W odróżnieniu od readf(...) ta metoda nie przyjmuje argumentów, tylko zwraca wartość:

 auto napis = plik.readln();

Metoda File.eof() informuje o tym czy odczytano wszystkie dane z pliku.

Kiedy już wczytamy wszystkie potrzebne dane i plik nie będzie nam dłużej potrzebny, zamykamy strumień używając metody close().

Dodatkowo potrzebna może być funkcja chomp() obcinająca z ciągu znak nowej linii (\n) na końcu. Znajduje się w module std.string.

Znamy zatem wszystkie instrukcje, które są potrzebne, by odczytywać pliki. Możemy zatem przejść do praktyki. Poniższy przykład prezentuje program, który wczytuje plik i wyświetla jego zawartość w konsoli:

import std.string;                                  // Funkcja chomp
import std.stdio;                                   // musimy zaimportować ten moduł, zawiera funkcję writefln()
import std.file;                                    // zawiera funkcję exists()

void main() {
  if (!exists("plik.txt")) {                        // Jeśli plik "plik.txt" nie istnieje
    writefln("Plik nie istnieje!");                 // to poinformuj o tym
    return;                                         // i wyjdź z funkcji (wyjście z funkcji main wyłącza program)
  }

  auto plik = new File("plik.txt", "r");            // otwórz plik do odczytu i utwórz strumień wczytujący
  while (!plik.eof()) {                             // dopóki w pliku są jeszcze nieodczytane dane
    auto line = chomp(plik.readln());               // wczytaj linię do zmiennej line
    writefln(line);                                 // wypisz na ekran zmienną line
  }
  plik.close();                                     // zamknij plik
}

By automatycznie zamknąć plik, trzeba wykorzystać szablon std.typecons.scoped.

Zapis danych do plikuEdytuj

Zapis danych do pliku przebiega dość podobnie, jak odczyt. Różnica polega na tym, że otwieramy plik do zapisu (patrz – tabelka) i, zamiast readf(), używamy metod writef() lub write(). Ponadto nie musimy martwić się o to, czy plik istnieje, ponieważ - jeśli nie - zostanie automatycznie utworzony.

Zaczniemy od omówienia (prawdopodobnie) prostszej metody – write(). Wystarczy że podamy dowolną ilość parametrów oddzielonych przecinkami by przeprowadzić zapis:

int i = 3;                                  //Zwykła liczba
auto s = "abc";                             //Ciąg znaków

plik.write("numer: ", i, "\nnapis: ", s);   //Bardzo intuicyjny zapis

Mniej intuicyjna, ale na dłuższą metę dużo wygodniejsza jest metoda writef, działająca analogicznie do readf. Najpierw wprowadzamy formatujący ciąg znaków a następnie zmienne w odpowiedniej kolejności. Kod działający dokładnie tak jak ten powyżej:

int i = 3;                                  //Zwykła liczba
auto s = "abc";                             //Ciąg znaków

plik.writef("numer: %d\nnapis: %s", i, s);   //Mniej intuicyjny zapis

Dokładny opis kodów %* można znaleźć m.in. w dokumentacji języka D lub tu. Do podstawowych operacji wystarczą te 3:

Format Znaczenie
%s Ciąg znaków
%d Liczba całkowita
%f Liczba zmiennoprzecinkowa

Operacje w systemie plikówEdytuj

Klasa File, wykonuje operacje wejścia i wyjścia na pojedynczym pliku. Potrafi ona również stworzyć nowy plik, jak również otworzyć plik do zapisywania. Inne operacje na plikach, oraz katalogach, czyli operacje na samym systemie plików, wykonuje się przy użyciu funkcji z modułu std.file.

W celu wykonywania takich operacji jak zmiana nazwy, przenoszenie, czy kopiowanie plików i folderów należy się posłużyć odpowiednimi funkcjami zawartymi w module std.file.

Oto lista najważniejszych funkcji modułu std.file (w argumentach podajemy nazwy plików):

  • rename(string src, string dest) - zmiana nazwy pliku z src na dest
  • remove(string name) - usunięcie pliku
  • copy(string src, string dest) - kopiowanie pliku z src do dest (aby przenieść plik, należy go skopiować, a następnie usunąć plik źródłowy)
  • ulong getSize(string name) - zwraca rozmiar pliku w bajtach
  • bool exists(string name) - sprawdza, czy plik istnieje (przykład przy odczycie z pliku)
  • bool isDir(string name) - sprawdza, czy nazwa wskazuje na katalog
  • bool isFile(string name) - sprawdza, czy nazwa wskazuje na plik
  • mkdir(string pathname) - tworzy katalog.
  • mkdirRecurse(string pathname) - tworzy katalog oraz wszystkie nieistniejące katalogi nadrzędne.
  • rmdir(string pathname) - usuwa katalog
  • rmdirRecurse(string pathname) - usuwa katalog wraz z całą zawartością
  • immutable(string[]) listdir(string pathname) - zwraca zawartość folderu (nazwy plików w tablicy)
  • immutable(string[]) listdir(string pathname, string pattern) - zwraca wszystkie nazwy plików pasujące do wzorca pattern. np. listdir("/usr/lib", "*.a"), zwróci tablice nazw plików mające rozszerzenie ".a" z katalogu "/usr/lib"