Przeszukiwanie ścieżki w poszukiwaniu plików za pomocą funkcji _findfirst i _findnext

Stronę tą wyświetlono już: 304 razy

Pobawiliśmy się już w zapisywanie i odczytywanie danych z i do pliku, teraz kolej przyszła pobuszować w katalogach i nauczyć się znajdowania plików i folderów zawartych w danej lokalizacji. Do tego celu można posłużyć się funkcjami _findfirst oraz _findnext, które umożliwiają przeszukiwanie danej ścieżki według podanego filtra przeszukiwania. Oto przykłady filtrów, wraz z ich opisem:

  • C:\* - wyszukiwanie wszystkich elementów zawartych w podanej lokalizacji;
  • C:\*.txt - wyszuka wszystkie pliki z rozszerzeniem txt;
  • C:\?1*.txt - wyszuka wszystkie pliki, które po pierwszym dowolnym znaku mają 1 w nazwie a za nią dowolną liczbę innych znaków i kończą się rozszerzeniem .txt (przykłady: "f1000.txt"; "11.txt" ...)

Oto przykładowy mały programik przeszukujący daną lokalizację oraz podfoldery i wyświetlająca katalogi i nazwy plików, w których dany plik został znaleziony:

Listing 1
  1. #include <iostream>
  2. #include <vector>
  3. #include <string>
  4. #include <windows.h>
  5. #include <io.h>
  6. using namespace std;
  7. void searchFile(string path, string filtr){
  8. _finddata_t f;
  9. intptr_t r;
  10. // przeszukiwanie podfolderów
  11. string searching_filtr = path; // tworzę filtr przeszukiwania folderów
  12. searching_filtr += "*"; // na końcu potrzebna jest *
  13. if((r=_findfirst(searching_filtr.c_str(),&f))>0){ // znajdowanie pierwszego folderu (to zawsze jest .
  14. while(!_findnext(r,&f)){
  15. if(strcmp(f.name, "..")){ // drugi to ..
  16. if(f.attrib & _A_SUBDIR){ // gdy dany element jest folderem to
  17. string p2 = path; // kopię ścieżki głównej robię
  18. p2 += f.name; // dodaję do tego nazwę folderu
  19. p2 += char(92); // dodaję \ na końcu
  20. searchFile(p2, filtr); // przeszukuję nową lokalizację
  21. }
  22. }
  23. }
  24. }
  25. _findclose(r); // zamykam przeszukiwanie
  26. // wyszukiwanie plików według podanego filtra
  27. searching_filtr = path; // tworzę ścieżkę dla filtra
  28. searching_filtr += filtr; // dodaję na koniec filtr
  29. if((r=_findfirst(searching_filtr.c_str(),&f))>0){ // piersze przeszukiwanie
  30. while(!_findnext(r,&f)){ // kolejne przeszukiwania
  31. if(!(f.attrib & _A_SUBDIR)){ // jak nie jest to folder
  32. cout<<path<<"t"<<f.name<<endl; // wypisuję nazwę znalezionego pliku
  33. }
  34. }
  35. }
  36. _findclose(r); // zamykam przeszukiwanie
  37. }
  38. int main(int size, char** pt){
  39. setlocale(LC_CTYPE,"Polish"); // polskie znaki
  40. string path = pt[0]; // wyciągam ścieżkę z nazwą programu
  41. string filtr = "*.*"; // to będzie filtr przeszukiwania
  42. path.erase(path.begin() + path.find_last_of(char(92),string::npos) + 1,path.end()); // wydziabuję ścieżkę dostępu do lokalizacji programu
  43. cout<<"Przeszukiwanie ścieżki: \""<<path<<"\" z filtrem wyszukiwania \""<<filtr<<"\""<<endl<<endl; // wyświetlam
  44. searchFile(path,filtr);
  45. cout<<endl<<endl<<"Wciśnij enter, aby zamknąć program...";
  46. cin.get();
  47. return 0;
  48. }

Warto przyjrzeć się nieco dokładniej strukturze _finddata_t, która ma nasępującą postać:

Listing 2
  1. struct _finddata64i32_t {
  2. unsigned attrib; // atrybuty pliku
  3. __time64_t time_create; // czas utworzenia pliku
  4. __time64_t time_access; // czas ostatniego dostępu do pliku
  5. __time64_t time_write; // czas ostatniego nadpisania pliku
  6. _fsize_t size; // rozmiar pliku
  7. char name[260]; // nazwa pliku (maksymalnie 259 znaków bo jeden jest przeznaczony na NULL)
  8. };
  9. typedef _finddata64i32_t _finddata_t;

W zależności od systemu może stosowane są nieco inne deklaracje tej samej struktury, stąd też zapis na końcu typedef _finddata64i32_t _finddata_t;.

Warto przyjrzeć się polu struktury attrib, dla którego ustawienia bitów mówią o rodzaju i dostępie do danego pliku:

Listing 3
  1. #define _A_NORMAL 0x00 // normalny plik - bez zakazu odczytu/zapisu
  2. #define _A_RDONLY 0x01 // tylko do odczytu
  3. #define _A_HIDDEN 0x02 // ukryty
  4. #define _A_SYSTEM 0x04 // systemowy
  5. #define _A_SUBDIR 0x10 // podfolder
  6. #define _A_ARCH 0x20 // archiwalny

Ponieważ dany typ pliku czy folderu może mieć kilka różnych atrybutów, konieczne jest wykorzystanie operatora koniunkcji bitowej & (czyli AND). Dla folderów taki test będzie wyglądał następująco:

Listing 4
  1. if(f.attrib & _A_SUBDIR){
  2. cout<<"To jest folder!!!"<<endl;
  3. }

Powyższe sprawdzenie zostało już oczywiście wykorzystane w programie.

Funkcje _findfirst i _findnext są nieco przestarzałe i nie obsługują szerokich znaków (typ wchar_t), co w konsekwencji może doprowadzić, że plik zawierający w nazwie znaki kodowania np. cyrylicy czy alfabetu greckiego nie będą poprawnie obsługiwane. Z tego względu warto też zwrócić uwagę na funkcje _wfindfirst oraz _wfindnext.

Komentarze