====== 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**
* ''fopen()'', ''fclose()'' - otwieranie i zamykanie pliku
* ''fgetc()'', ''fputc()'' - odczyt i zapis znaku (bajtu)
* ''fgets()'' - odczyt linii tekstu
* ''fscanf()'', ''fprintf()'' - odczyt i zapis formatowany (np. liczby)
* ''fread()'', ''fwrite()'' - odczyt i zapis ciągu bajtów
===== 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.
#include
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: {{zajecia:pp1_2019_1:dane: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 ''[[https://pl.wikibooks.org/wiki/C/fgets|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:
* **stdin** strumień wejściowy otworzony w trybie do odczytu
* **stdout** strumień wyjściowy otworzony w trybie do zapisu
* **stderr** wyjście diagnostyczne otworzone w trybie so zapisu
==== 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''.
#include
#include
#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"%%''.
#include
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.'
#include
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: {{zajecia:pp1_2019_1:dane:output.txt}}\\
Więcej informacji o funkcjach służących do manipulacji na plikach znajdziesz w dokumentacji biblioteki **[[http://pl.wikibooks.org/wiki/C/Biblioteka_standardowa/Indeks_tematyczny#stdio.h|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**
* nazwa pliku źródłowego (zawiera oryginalny tekst)
* nazwa pliku docelowego (w tym pliku zapisz wynik)
**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