Práce s textovými soubory

Když už umíte soubor otevřít a zavřít, je na čase do něj také něco zapsat a něco z něj přečíst. V této kapitole se zaměřím na funkce, které čtou a zapisují řetězce. Při otevření souboru funkcí fopen() budu soubor otvírat v textovém režimu. To však neznamená, že by zde popisované funkce nemohli pracovat v binárním režimu a s binárními (netextovými) soubory. Ale až si prohlédnete příklady, pochopíte, že by to nemělo moc velký smysl.

Zápis do textového souboru

Jako programátoři jistě víte, že textový soubor není dán tím, že má příponu „.txt“ ale svým obsahem – obsahuje jen text. Tedy i čísla která chcete zapsat budete zapisovat jako text. Na rozdíl od binárních souborů, kde se čísla zapisují tak, jak jsou uloženy v paměti, tedy v bajtech.

První funkcí pro zápis do textového souboru, se kterou se nyní seznámíme, je funkce fprintf(). Tato funkce je na chlup stejná, jako funkce printf(). Jediný rozdíl je v tom, že funkce fprintf() má navíc parametr stream, kterým určujete, do kterého souborového proudu chcete zapisovat.

Pokud tímto souborovým proudem bude stdout, pak se chování funkce nebude lišit od funkce printf() opravdu v ničem.

Další možností je zápis do standardního chybového proudu stderr, nebo do našeho otevřeného souborového proudu otevřeného funkcí fopen().

int  fprintf(FILE *stream, const char *format, ...);

Kromě této funkce je možné také použít funkce fputs() pro tisk řetězce, fputc() a putc() pro tisk znaku. Jejich deklarace jsou následující:

int fputs(const char *s, FILE *stream);
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);

Rozdíl mezi fputc() a putc() je v tom, že fputc() je funkce, zatímco putc() je implementována jako makro (proto je lepší používat fputc()).

Funkce fputc() a putc() vracejí zapsaný znak, nebo EOF v případě chyby. Funkce fputs() nezáporné číslo v případě úspěchu (tedy 0 a více), při chybě EOF.

V následujícím jednoduchém příkladě si vyzkoušíte použití těchto funkcí. Nebudu v něm kontrolovat návratové hodnoty funkcí (pro co největší jednoduchost), vy byste však ve svých příkladech na kontrolu nikdy neměli zapomenout. Program bude zapisovat na konec souboru text zadaný od uživatele.

/*------------------------------------------------*/
/* c32/fprintf1.c                                 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

#define NAZEV "textak.txt"

int main(void)
{
    FILE *soubor;
    char text[255];

    soubor = fopen(NAZEV, "a+"); /* soubor se otevre pro aktualizaci,
                                    neexistujici soubor se vytvori */

    do {
        fputs("Zadejte slovo, ktere chcete zapsat do souboru\n"
              "a stisknete ENTER, nebo \"q\" pro ukonceni: ", stdout);
        scanf("%254s", text);
        if (!strcmp(text, "q"))
            break;
        printf("Zapisuji >> %s <<\n", text);
        fprintf(soubor, ">> %s <<\n", text);
    } while (1);

    fclose(soubor);

    return 0;
}

/*------------------------------------------------*/
Visual Studio

Makro _CRT_SECURE_NO_WARNINGS je tu kvůli funkci fopen(), viz scanf().

Možný výstup z programu:

Zadejte slovo, ktere chcete zapsat do souboru
a stisknete ENTER, nebo "q" pro ukonceni: jedna
Zapisuji >> jedna <<
Zadejte slovo, ktere chcete zapsat do souboru
a stisknete ENTER, nebo "q" pro ukonceni: dve
Zapisuji >> dve <<
Zadejte slovo, ktere chcete zapsat do souboru
a stisknete ENTER, nebo "q" pro ukonceni: honza jde
Zapisuji >> honza <<
Zadejte slovo, ktere chcete zapsat do souboru
a stisknete ENTER, nebo "q" pro ukonceni: Zapisuji >> jde <<
Zadejte slovo, ktere chcete zapsat do souboru
a stisknete ENTER, nebo "q" pro ukonceni: q

V aktuálním adresáři se vytvoří soubor textak.txt, který bude mít takovýto obsah:

>> jedna <<
>> dve <<
>> honza <<
>> jde <<

Čtení z textového souboru

Když už umíte do textového souboru zapisovat, proč se nenaučit z něj číst. Je to stejně snadné. K čtení z textového souboru bude nejlepší použít funkci fscanf(). Tato funkce je na chlup stejná, jako funkce scanf(). Jediný rozdíl je v tom, že funkce scanf() má navíc argument stream, kterým určujete, ze kterého souborového proudu chcete číst.

Pokud tímto souborovým proudem bude stdin, pak se chování funkce nebude lišit od funkce scanf() opravdu v ničem.

int fscanf(FILE *stream, const char *format, ...);

Kromě této funkce je možné také použít funkce fgets() pro načtení řetězce, fgetc() a getc() pro načtení znaku. Jejich deklarace jsou následující:

char *fgets(char *s, int size, FILE *stream);
int fgetc(FILE *stream);
int getc(FILE *stream);

Rozdíl mezi fgetc() a getc() je pouze v implementaci těchto funkcí překladačem. Doporučuji používat vždy fgetc().

V příkladě otevřu textový soubor pro zápis (zkrátím jej na nulovou délku), zapíši text a čísla do souboru, pak soubor uzavřu, otevřu jej pro čtení, a jeho obsah načtu a vypíšu.

  1. /*------------------------------------------------*/
  2. /* c32/fscanf1.c                                  */
  3. #define _CRT_SECURE_NO_WARNINGS
  4. #include <errno.h>
  5. #include <stdio.h>
  6. #include <string.h>
  7.  
  8. #define NAZEV "textak.txt"
  9.  
  10. int main(void)
  11. {
  12.     FILE *soubor;
  13.     size_t i;
  14.     int c;
  15.     float x, y, z;
  16.     char text[255];
  17.  
  18.     if ((soubor = fopen(NAZEV, "w")) == NULL) {
  19.         fprintf(stderr, "Chyba otevreni do souboru %s pro zapis\n",
  20.         NAZEV);
  21.         return errno;
  22.     }
  23.  
  24.     /* nejdrive do souboru neco zapisi */
  25.     fputs("Seznam zlomku:\n", soubor);
  26.     for (i = 1; i <= 5; i++) {
  27.         fprintf(soubor, "%f %f %f\n", 100.0, (float) i, 100.0 / i);
  28.     }
  29.     fclose(soubor);
  30.     /* a ted to zpatky nactu */
  31.     if ((soubor = fopen(NAZEV, "r")) == NULL) {
  32.         fprintf(stderr, "Chyba otevreni souboru %s pro cteni\n",
  33.         NAZEV);
  34.         return errno;
  35.     }
  36.  
  37.     /* prvni radek nactu znak po znaku */
  38.     do {
  39.         c = fgetc(soubor);
  40.         fputc(c, stdout);
  41.     } while (c != '\n');
  42.  
  43.     /* druhy radek (prvni radek s cisly) nactu naraz */
  44.     fgets(text, sizeof(text), soubor);
  45.     fputs(text, stdout);
  46.  
  47.     /* zbytek nactu po cislech */
  48.     for (i = 1; i <= 4; i++) {
  49.         fscanf(soubor, "%f %f %f", &x, &y, &z);
  50.         printf("%f/%f = %f\n", x, y, z);
  51.     }
  52.  
  53.     fclose(soubor);
  54.     return 0;
  55. }
  56.  
  57. /*------------------------------------------------*/

Výstup z programu:

Seznam zlomku:
100.000000 1.000000 100.000000
100.000000/2.000000 = 50.000000
100.000000/3.000000 = 33.333332
100.000000/4.000000 = 25.000000
100.000000/5.000000 = 20.000000

Kontrola chyb – feof(), ferror(), clearerr()

Na dalším příkladu ukáži, co všechno je třeba kontrolovat při práci se soubory. Pro začátek si zopakujte vše, co jste se dozvěděli o knihovně <errno.h>. U každé funkce pracující se soubory máte definovanou návratovou hodnotu, pomocí níž můžete (nebo spíš musíte) kontrolovat, zda nedošlo k chybě. Při práci s datovými proudy máte k dispozici ještě další funkce, které vám pomohou se vyvarovat chyb. Tyto funkce pracují s datovým proudem stream a je zcela jedno, jestli jde o textový, nebo binární přístup.

int feof(FILE *stream);
void clearerr(FILE *stream);
int ferror(FILE *stream);

Nejdůležitější je funkce feof(), která vás informuje o tom, zda jste dosáhli konce souboru. Pokud nejste na konci souboru, vrací 0, jinak kladnou hodnotu (1). Často se používá v cyklech, kde se čte, dokud není dosaženo konce „souboru“. Její použití je velice jednoduché a velice časté.

Funkce ferror() testuje indikátory chyb (obdobně jako feof() indikátor konce souboru). Pokud nedošlo k chybě, vrací 0, jinak kladnou hodnotu.

Funkce clearerr() dokáže jako jediná odstranit indikátor konce souboru a indikátory chyb v datovém proudu. To může být užitečné například ve chvíli, kdy se dostanete na konec souboru, ale pak se přesunete na jeho začátek (jak to udělat se dozvíte později), nebo pro obnovení zápisu do proudu v případě uvolnění místa na zaplněném disku.

V příkladu máte za úkol otevřít soubor cisla.txt a sečíst všechna čísla, která jsou v něm zapsána.

  1. /*------------------------------------------------*/
  2. /* c32/feof1.c                                    */
  3. #define _CRT_SECURE_NO_WARNINGS
  4. #include <errno.h>
  5. #include <stdio.h>
  6. #include <string.h>
  7.  
  8. #define NAZEV "cisla.txt"
  9.  
  10. int zavri_soubor(FILE * soubor)
  11. {
  12.     if (fclose(soubor) == EOF) {
  13.         fprintf(stderr, "%s %i", __FILE__, __LINE__);
  14.         perror(NULL);
  15.         return errno;
  16.     }
  17.     return 0;
  18. }
  19.  
  20. int main(void)
  21. {
  22.     FILE *soubor;
  23.     float f, suma;
  24.  
  25.     if ((soubor = fopen(NAZEV, "rt")) == NULL) {
  26.         fprintf(stderr, "%s %i:", __FILE__, __LINE__);
  27.         perror(NULL);
  28.         return errno;
  29.     }
  30.     suma = 0.0;
  31.     do {
  32.         /* ma se nacist jedno cislo */
  33.         if (fscanf(soubor, "%f", &f) == 1) {
  34.             printf("%.1f ", f);
  35.             suma += f;
  36.         /* dosahli jsme konce souboru */
  37.         } else if (feof(soubor)) {
  38.             break;
  39.         /* preskoci retezec, ktery neni cislo */
  40.         } else if (fscanf(soubor, "%*s") != 0) {
  41.             fprintf(stderr, "%s %i:", __FILE__, __LINE__);
  42.             perror(NULL);
  43.             return zavri_soubor(soubor);
  44.         }
  45.     } while (1);
  46.  
  47.     (void) zavri_soubor(soubor);
  48.     printf("\nSuma = %.3f\n", suma);
  49.     return 0;
  50. }
  51.  
  52. /*------------------------------------------------*/

Výhoda použití perror() je v tom, že lze pomocí knihovny <locale.h> nastavit jazyk, jakým na vás bude program mluvit. Knihovnou <locale.h> se budu zabývat až v části věnované programování pod Linuxem.

Podívejte se, jak bude program reagovat, pokud soubor cisla.txt nebude existovat.

kod.c 26:No such file or directory

Nebo když nebudete mít přístupová práva k čtení souboru:

kod.c 26:Permission denied

Podívejte se na výstup programu, kdy soubor cisla.txt existuje a mát následující obsah:

text 11.257 te123xt 20.447
18.3 text 30 text ? 20

Výstup:

11.3 20.4 18.3 30.0 20.0
Suma = 100.004

Zkuste za domácí úkol část kódu kde se otvírá soubor cisla.txt zakomentovat a místo něj přiřadit do proměnné soubor stdin:

soubor = stdin;
Komentář Hlášení chyby
Created: 29.8.2003
Last updated: 25.6.2014
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..