Otevírání souborů
Abychom mohli s nějakým souborem začít pracovat, musíme ho nejprve v našem programu otevřít, aby
byl vytvořen souborový deskriptor, do kterého pak můžeme zapisovat či z něj číst data. K tomu slouží
funkce fopen
, která má následující
signaturu:
FILE* fopen(const char* filename, const char* mode);
Cesta k souboru
Jako svůj první parametr funkce fopen
očekává řetězec s cestou k souboru, který má být otevřen.
Cestu můžete zadat dvěma způsoby:
- Absolutní cesta (absolute path) je cesta, která začíná kořenovým adresářem souborového
systému, například
/home/student/upr/soubor.txt
1. Aby byla cesta absolutní, musí na Linuxu začínat dopředným lomítkem.1Na Windows by podobná cesta mohla vypadat například takto:
C:\Users\student\upr\soubor.txt
. - Relativní cesta (relative path) se vyhodnotí relativně k tzv. pracovnímu adresáři
(working directory) běžícího programu. Pokud spustíte váš program z terminálu, tak se pracovní
adresář implicitně nastaví na adresář, ze kterého jste program spustili. Pokud tedy například spustíte
váš program z adresáře
/home/student/upr
a funkcifopen
předáte cestusoubor.txt
, tak se funkce pokusí otevřít soubor na cestě/home/student/upr/soubor.txt
.
Při zadávání cesty můžete využít speciální odkazy .
a ..
, které jsou užitečné zejména u relativních
cest:
- Odkaz
.
se odkazuje na současný adresář,./soubor.txt
je tedy to samé jakosoubor.txt
. - Odkaz
..
se odkazuje na rodičovský adresář,../data/abc.txt
tedy říká:Podívej se do rodičovského adresáře, tam vyhledej adresář data a v něm soubor abc.txt
.
Nepokoušejte se však zadávat cesty k neexistujícím adresářům. fopen
sice umí vytvořit nový soubor
(pokud použijete odpovídající mód), neexistující adresář za vás nicméně nevytvoří.
Doposud jsme používali prvky C, které byly vesměs nezávislé na použitém operačním systému. Jakmile ale naše programy začnou interagovat se souborovým systémem (file system), budeme muset začít respektovat zákonitosti operačního systému, na kterém náš program poběží. Proto například u cesty k souborům vždy používejte dopředná lomítka (
/
) pro oddělování adresářů, pokud program budete spouštět na Linuxu.
Mód otevření
Druhým parametrem funkce fopen
je řetězec, jehož obsah určuje, v jakém módu (mode) se má
soubor otevřít. Kompletní seznam všech kombinací módů naleznete v
dokumentaci, zde je seznam běžných variant:
Mód | Možné operace | Co se stane, když už soubor existuje? | Co se stane, když soubor neexistuje? |
---|---|---|---|
"r" | Čtení | chyba | |
"w" | Zápis | obsah souboru je smazán | soubor je vytvořen |
"a" | Zápis na konci | soubor je vytvořen | |
"r+" | Čtení, zápis | chyba | |
"w+" | Čtení, zápis | obsah souboru je smazán | soubor je vytvořen |
"a+" | Čtení, zápis na konci | soubor je vytvořen |
Při otevírání souboru si musíte rozmyslet, jestli z něj chcete číst, zapisovat do něj nebo provádět
obojí. Zároveň si musíte určit, jestli chcete soubor vytvořit v případě, že neexistuje, popřípadě
jestli má být jeho obsah smazán, pokud už existuje. Podle těchto vlastností si pak zvolte odpovídající
mód otevření souboru. Nejběžněji používanými módy jsou "r"
pro čtení ze souboru a "w"
pro zápis
do souboru.
Textový vs binární režim
Pokud použijete jeden ze základních módů, soubor se otevře v tzv. textovém režimu. V tomto režimu
dochází ke konverzi určitých bytů při čtení a zápisu ze souboru. Asi nejdůležitějším znakem, který
je takto konvertován, je '\n'
, neboli odřádkování (newline). Různé operační systémy totiž
při interpretaci souborů používají různé znaky pro odlišení situace, kdy má dojít k přesunu kurzoru
na nový řádek:
LF
: Linux a macOS2 používají pro konec řádku přímo ASCII znakLF (line feed)
, který lze v C zapsat jako'\n'
.2V dávných dobách používal Mac OS pro odřádkování pouze znak
CR
.CRLF
: Windows používá pro konec řádku dvojici ASCII znakůCR (carriage return)
aLF
(v tomto pořadí).CR
lze v C zapsat jako'\r'
.
Na Windows tak při zápisu do souborů otevřených v textovém módu dojde ke konverzi znaku odřádkování
\n
na dvojici znaků \r\n
. Stejně tak při načítání dat ze souboru se dvojice znaků \r\n
převedou
na \n
. Na Linuxu textový mód v podstatě nic nedělá, protože se zde pro odřádkování používá přímo
znak \n
.
Pokud byste však chtěli mít jistotu, že opravdu k žádné konverzi nedojde, a budete zapisovat data,
která nemají být interpretována jako text, můžete na konec módu přidat znak b
. Poté se soubor
otevře v tzv. binární režimu, kde k žádné konverzi nedochází. Mód "rb"
tak například říká
Otevři soubor pro čtení v binárním režimu
.
Pokud byste chtěli explicitně říct, že se má použít textový režim, můžete na konec módu přidat znak
t
. Například mód"rt"
je ekvivalentní s módem"r"
a označuje otevření souboru pro textové čtení.
Ošetření chyb
Jakmile řeknete funkci fopen
jaký soubor (a v jakém módu) má otevřít, funkce jej otevře a vrátí
vám ukazatel na strukturu FILE
, pomocí které můžete se souborem dále pracovat3. Stejně jako
u jakékoliv práce se vstupem a výstupem i při práci se soubory však může často docházet k různým
chybám.
3FILE
je tzv. neprůhledná (opaque) struktura deklarovaná ve standardní knihovně C.
Nebudete přistupovat k žádným jejím členům, pouze budete ukazatel na ni posílat do různých funkcí
pro práci se soubory, abyste určili, s jakým (otevřeným) souborem chcete pracovat.
Pokud byste se například pokoušeli otevřít neexistující soubor v módu pro čtení "r"
, dojde k chybě.
V takovém případě vám funkce fopen
vrátí adresu nula (tzv. NULL
ukazatel). Po každém pokusu o
otevření souboru byste tak měli ověřit, zdali se otevření opravdu podařilo nebo ne. Pokud při otevření
došlo k chybě, tak se do globální proměnné
errno
uloží číslo, které identifikuje, o jaký typ chyby šlo4.
K proměnné budete mít přístup, pokud do svého programu vložíte
soubor <errno.h>
. Pomocí funkce strerror
ze souboru
<string.h>
pak můžete získat řetězec, který danou chybu popisuje:
4Seznam různých chybových hodnot, které se můžou v errno
objevit na operačním systému Linux,
můžete naleznout například zde.
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main() {
FILE* soubor = fopen("soubor.txt", "r");
if (soubor == NULL) {
printf("Doslo k chybe pri otevirani souboru: %s\n", strerror(errno));
return 1; // došlo k chybě, vrátíme 1 jako chybový stav programu
}
return 0;
}
Použití assert
Pokud píšete malý program a nechce se vám ručně každou chybu ošetřovat, můžete využít
makro assert
ze souboru <assert.h>
.
Toto makro očekává pravdivostní hodnotu a kontroluje, zdali platí (assert
znamená
ujisti se, že platí …
). Pokud hodnota neplatí, tj. vyhodnotí se na 0
či false
, tak dojde k
okamžitému ukončení vašeho programu. Nebudete tak sice moct ovlivnit vypsanou chybovou hlášku, ale
ošetření chyby se značně zjednodušší:
FILE* soubor = fopen("soubor.txt", "r");
assert(soubor); // pokud je `soubor` roven `NULL`, program se zde ukončí
Ošetření chyb je dobré nepodceňovat. Pokud chybu ošetříte okamžitě po jejím možném vzniku (i kdyby to mělo být okamžitým vypnutím programu), tak bude mnohem jednodušší zjistit, kde v kódu a proč vznikla. Jinak se může jednoduše stát, že k chybě sice dojde, ale program bude pokračovat vesele dál. Tato chyba pak může v průběhu programu způsobit kaskádu dalších chyb, které nakonec dříve či později povedou k "spadnutí" nebo špatnému fungování programu. V takové situaci bude mnohem náročnější zjistit, kde vznikla původní chyba, která vše způsobila, protože program může spadnout na úplně jiném místě v kódu.
Zavření souboru
Jakmile se souborem přestanete pracovat, je nutné ho zavřít. K tomu slouží funkce
fclose
:
FILE* soubor = fopen("soubor.txt", "w");
// zápis/čtení ze souboru…
fclose(soubor);
Funkce fclose
vrací číselnou hodnotu, která oznamuje, zdali funkce proběhla v pořádku nebo ne.
Pokud funkce vrátí 0
, tak se soubor úspěšně uzavřel. I u zavírání souborů bychom tedy měli mít
alespoň základní ošetření chyb5:
5Operátor !
provede logickou negaci. Pokud jej použijeme s hodnotou 0
, vrátí hodnotu 1
.
Pokud jej použijeme s jakoukoliv jinou hodnotou, vrátí hodnotu 0
. Pokud tedy funkce fclose
vrátí
cokoliv jiného než 0
, assert
ukončí program.
assert(!fclose(soubor));
Pokud bychom soubor nezavřeli, tak se například může stát, že kvůli použitému bufferování by se data, která jsme do souboru zapsali, nemusela objevit na souborovém systému.