Spis treści

Operacje na plikach

Praca na plikach możliwa jest dzięki funkcjom dostępnym w bibliotece stdio.h. Chcąc odczytać dane z pliku (lub zapisać dane do pliku) najpierw należy uzyskać dostęp do pliku za pomocą funkcji fopen(), która otwiera plik i zwraca zmienną typu FILE*. Uzyskany w ten wskaźnik daje dostęp do pliku pozwalając na sekwencyjny odczyt lub zapis danych do wskazanego pliku. Jest on argumentem wszystkich funkcji operujących na plikach. Po zakończeniu pracy na pliku należy go zamknąć za pomocą funkcji fclose().

Przydatne funkcje

Otwieranie i zamykanie plików: fopen, fclose

Funkcja fopen otwiera plik o nazwie nazwa_pliku.

FILE *fopen(char *nazwa_pliku, char *tryb);

Pierwszy argument nazwa_pliku jest napisem zawierającym ścieżkę do pliku. Może to być ścieżka bezwzględna (np. w Windows "C:\\Users\a.txt", w Unix/Linux "/home/user/a.txt") lub względna zależna od bieżącego katalogu roboczego. Jeżeli podamy tylko nazwę pliku (bez pełnej ścieżki) to powinien on się znajdować w tym samym katalogu co plik wykonywalny.

Argument tryb to napis, który określa tryb dostępu do pliku. Oto lista dostępnych trybów dla plików tekstowych:

Tryb
"r" Odczyt pliku od początku.
Jeżeli plik nie istnieje to funkcja fopen zwróci NULL.
"w" Zapis (nadpisz od początku)
Jeżeli plik nie istnieje to zostanie utworzony.
Jeżeli plik istnieje to jego zawartość zostanie skasowana
"a" Dodaj na końcu

Uwaga: w przypadku wystąpienia błędu przy próbie uzyskania dostępu do pliku funkcja fopen zwraca wartość NULL. Przyczyną błędu może być zła nazwa pliku lub niepoprawna ścieżka, brak uprawnień do otwierania (lub edycji) pliku lub nawet brak miejsca na nośniku. Ponieważ tego typu sytuacje mogą występować często dlatego należy zawsze sprawdzić czy operacja fopen zakończyła się sukcesem.

Po zakończeniu pracy na pliku należy go zamknąć za pomocą funkcji fclose

int fclose (FILE *plik);

Odczyt pojedynczego znaku ze strumienia: fgetc

Pojedynczy znak z pliku możemy pobrać za pomocą funkcji fgetc.

int fgetc(FILE *f);

Argumentem funkcji jest wskaźnik f do pliku uzyskany wcześniej za pomocą fopen. Plik powinien być otworzony w trybie do odczytu. Funkcja fgetc zwraca wartość EOF, gdy strumień danych z pliku jest pusty (koniec pliku).

Umieszczenie pojedynczego znaku w strumieniu: fputc

Funkcja fputc umieszcza znak c w pliku f

int fputc(char c, FILE* f);

Wykrywanie końca pliku: feof

Funkcja feof pozwala sprawdzić, czy podczas odczytu pliku f dotarliśmy do jego końca.

int feof(FILE *plik);

Funkcja zwraca wartość 1 gdy osiągnięto koniec pliku, w przeciwnym wypadku zwracana jest wartość 0.

Przykład: Czytanie sekwencji znaków z pliku

Poniższy program odczytuje wszystkie znaki (bajty) z pliku plik.txt i wyświetla je na ekranie. Jeżeli plik wejściowy nie istnieje lub gdy nastąpił jakikolwiek błąd przy otwieraniu pliku wówczas wyświetlany jest komunikat o błędzie i program kończy swoje działanie.

znaki.c
#include<stdio.h>
 
int main()
{
   FILE *plik = NULL;
   int znak;
 
   plik = fopen( "plik.txt", "r" );
   if( plik == NULL )
   {
      printf("Wystapil blad otczytu pliku\n");
      return 1;
   }
 
   while( feof(plik) == 0 )
   {
      znak = fgetc(plik);
      if (znak != EOF) printf("%c", znak);
   }
   fclose( plik );
 
   return 0;
}

Przykładowy plik wejściowy: plik.txt. Należy umieścić go w katalogu w którym znajduje się plik wykonywalny programu.

Ćwiczenie: Formatowanie tekstu

Napisz program, który wypisze na wyjściu zawartość pliku o nazwie plik.txt w liniach o długości N znaków. Wartość N podaje użytkownik na początku działania programu. Wynik zapisz również w pliku o nazwie wynik.txt.

Dane: liczba całkowita N oraz plik z tekstem
Wynik: zawartość pliku wyświetlona na ekranie oraz zapisana do pliku wynik.txt, gdzie wiersze mają długość N znaków.

Przykład
Jeżeli plik wejściowy zawiera tekst:

The origin of C is closely tied to the development of the Unix operating system

to po uruchomieniu programu uzyskamy wynik:

n=2
Th
e 
or
ig
in
 o
f  
C
is
...

Odczyt linii tekstu z pliku: fgets

Funkcja fgets odczytuje linię tekstu z pliku.

char* fgets (char *s, int n, FILE *f);

Funkcja czyta linię tekstu z pliku f i umieszcza ją w postaci napisu (dodaje znak '\0') w tablicy s. Argument n ogranicza maksymalna liczbę znaków jaką wczyta funkcja zapobiegając przepełnieniu bufora dla zbyt długich linii wejściowych, tzn. funkcja przeczyta maksymalnie n-1 znaków.
Wartością zawracana jest s lub wartość NULL, gdy nastąpi koniec pliku lub błąd odczytu.

Jest to więc odpowiednik funkcji czytaj_linie z poprzednich zajęć, z tą różnicą, że gets() operuje na strumieniu f, zaś czytaj_linie() pobiera tekst ze standardowego wejścia.

Uwaga: funkcja fgets() pozostawia znak '\n' na końcu napisu.

stdin, stdout, stderr

W pliku stdio.h zadeklarowane są zmienne, które dają dostęp do strumieni standardowych procesu:

Przykład: Numerowanie linii

Poniższy program wypisuje ponumerowane linie pliku o nazwie podanej przez użytkownika. Nazwa pliku wejściowego odczytywana jest za pomocą fgets() ze standardowego wejścia stdin.

nl.c
#include<stdio.h>
#include<string.h>
 
#define MAX 1000
 
int main()
{
   char linia[MAX];
   int i=1, n;
   FILE *plik;
 
   printf("Podaj nazwe pliku: ");
   fgets(linia, MAX, stdin);
 
   /*  usuwanie znaku '\n' z konca napisu */
   n = strlen(linia);
   linia[n-1] = '\0';
 
   plik = fopen(linia, "r");
   if(!plik) 
   {
      printf("Blad odczytu pliku: %s\n", linia);
      return 1;
   }
 
   i = 0;
   while(fgets(linia, MAX, plik) != NULL)
   {
      printf("%d : %s", i, linia);
      i++;
   }
 
   fclose(plik);
}

Ćwiczenie: Szukanie wzorca (grep)

Napisz program, który wyszuka w pliku tekstowym linie zawierające podany wzorzec. Program pobiera od użytkownika 2 linie tekstu, pierwsza zawiera nazwę pliku wejściowego a druga - wzorzec do wyszukiwania. Następnie z podanego pliku wypisuje te linie, które zawierają wzorzec. Jeśli podany plik nie istnieje to program wypisuje stosowny komunikat błędu i kończy swoje działanie.

Wskazówka: do wyszukiwania wzorca w tekście można użyć funkcję strstr()

Przykład działania
Dla pliku o nazwie abacus.txt' zawierającego tekst

The abacus, also called a counting frame, is a calculating tool 
that was in use in the ancient Near East, Europe, 
China, and Russia, centuries before the 
adoption of the written Hindu–Arabic numeral system.
The exact origin of the abacus is still unknown. 

oraz wprowadzonych przez użytkownika danych postaci

podaj nazwe pliku: abacus.txt
podaj wzorzec: abacus

program wypisze

The abacus, also called a counting frame, is a calculating tool 
The exact origin of the abacus is still unknown. 

Zapis formatowany do pliku tekstowego: fprintf

Funkcja fprintf służy do zapisu tekstu do pliku z formatowaniem. Jej działanie jest takie samo jak funkcji printf() z jedną rożnica - formatowany tekst zamiast na ekran trafia do pliku f otworzonego w trybie do zapisu.

int fprintf(FILE *f, char *format, ...);

Przykład 3: Zapis wartości do pliku

Poniższy program demonstruje zapis wartości całkowitej 42, wartości rzeeczywistej 4.13 oraz napisu "Witaj swiecie" do pliku o nazwie "output.txt".

zapis.c
#include<stdio.h>
int main()
{
   int a = 42;
   float x = 3.14;
   char napis[]="Witaj swiecie\n";
   FILE* plik;
 
   plik = fopen("output.txt", "w");
   if (plik)
   {
      fprintf(plik, "a = %d\n", a);
      fprintf(plik, "x = %f\n", x);
      fprintf(plik, "%s\n",  napis);
 
      fclose(plik);
   }
   else printf("Blad zapisu do pliku\n");
 
   return 0;
}

Odczyt formatowany z pliku tekstowego: fscanf

Do odczytu z pliku danych z uwzględnieniem ich formatowania służy funkcja fscanf. Działa ona identycznie jak znana nam już funkcja scanf, z tą rożnica, że odczyt danych następuje z pliku f otworzonego w trybie do odczytu.

int fscanf(FILE *f,  char *format, ...);

Przykład 4: Odczyt wartości z pliku

Poniższy program demonstruje sposób użycia funkcji fscanf do odczytania danych zapisanych za pomocą programu z poprzedniego przykładu.'

odczyt.c
#include<stdio.h>
 
int main()
{
   int a;
   float x;
   char napis[100]; 
   FILE* plik;
 
   plik = fopen("output.txt", "r");
   if (!plik)
   {
      printf("Blad odczytu do pliku\n");
      return 1;
   }
 
   fscanf(plik, "a = %d\n", &a);
   fscanf(plik, "x = %f\n", &x);
   fgets(napis, 100, plik);
   fclose(plik);
 
   printf("a = %d\n", a);
   printf("x = %f\n", x);
   printf("%s\n", napis);
 
   return 0;
}

Przykładowy plik z danymi do odczytu: output.txt

Więcej informacji o funkcjach służących do manipulacji na plikach znajdziesz w dokumentacji biblioteki stdio.h

Zadanie - Szyfr - znaki przestawne

Napisz program, który zaszyfruje zawartość pliku tekstowego poprzez zamianę miejscami kolejnych par znaków (bajtów). Jeżeli plik wejściowy zawiera nieparzystą liczbę znaków, wówczas ostatni znak pozostaje na swoim miejscu. W przypadku problemów z odczytaniem pliku wejściowego program wyświetla stosowny komunikat błędu i kończy swoje działanie. Nazwę pliku wejściowego (lub ścieżkę do pliku) oraz nazwę pliku docelowego (do którego zostanie zapisany wynik) podaje użytkownik na początku działania programu.

Dane wejściowe programu

Wynik działania: powstaje plik docelowy z zaszyfrowaną zawartością pliku źródłowego lub pojawia sie komunikat błędu, jeżeli plik źródłowy nie istnieje lub nie można odczytać jego zawartości.

Przykład
Jeżeli w katalogu roboczym znajduje sie plik input.txt o treści:

Lorem ipsum dolor sit amet,
consectetur adipiscing elit,
sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua.

to uruchomienie programu z następującymi danymi:

Podaj nazwe pliku zrodlowego: input.txt
Podaj nazwe pliku docelowego: output.txt 

utworzy plik o nawie output.txt zawierający taką treść

oLer mpius modol ris tmate
,ocsnceetut rdapisiicgne il,ts
ded  oiesuom detpmroi cndidinu
ttul baro eted loro eamng alaqiau.

Rozwiązanie umieść w Moodle pod adresem: https://moodle.umk.pl/WFAIIS/mod/assign/view.php?id=2151