Ciele
- Základná práca so súborom
- Riedke súbory
Postup
Krok 1: Otvorenie súboru
Príprava
Stiahnite si archív k cvičeniu .
Obsah archívu.
.
└── 02
├── Makefile // vaše súbory môžete skompilovať: make nazov
├── o2-3.c //.c súbory obsahujú napríklad, kostru riešenia programu k úlohe
├── o3-2.c
├── o3-3.c
├── prepare-02.sh
├── pr1-1.c
├── pr2-1.c
├── pr2-2.c
├── pr3-1.c
├── pr4-1.c
├── remove-02.sh
├── testy //testy k úlohám
│ ├── u1-1-test.sh
│ ├── u1-2-test.sh
│ ├── u2-1-test.sh
│ ├── u2-2-test.sh
│ ├── u3-1-test.sh
│ └── u4-1-test.sh
├── u1-1.c
├── u1-2.c
├── u2-1.c
├── u2-2.c
├── u3-1.c
└── u4-1.c
Pracujte v adresári pomenovanom podľa cvičenia napr. ak pri rozbalení 02.zip
vznikne 02
, pracujte v adresári 02
.
Spustite skript prepare-02.sh
, ktorý vytvorí potrebné súbory k cvičeniu: ./prepare-02.sh
.
Poznámka
Ak súbor, s ktorým pracuje úloha nechtiac zmeníte, môžete ho nanovo vytvoriť podľa pokynov v časti //príprava
uvedenej pri úlohe alebo zmazaním celej štruktúry súborov, s ktorými cvičenie pracuje skriptom remove-02.sh
a následne spustením prepare-02.sh
Poznámka
Ak je úloha testovaná, test je možné spustiť nasledovne:
príklad: otestovanie riešenia úlohy 1.1, váš program má názov v tvare u1-1
./tests/u1-1-test.sh
Teória - open
Ak má byť obsah súboru zmenený počas behu programu, je potrebé pre získanie prístupu k nemu alebo pre jeho vytvorenie „požiadať“ operačný systém. V oboch prípadoch je prvým krokom otvorenie súboru.
Služba jadra open()
Služba jadra open() otvorí súbor na danej ceste vo zvolenom režime.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(char *pathname, int flags, mode_t mode)
Parametere | Popis |
---|---|
pathname | názov alebo cesta k súboru |
flags | flagy - spresnenie
|
mode | pri vytvorení nového súboru, sú ním určené prístupové práva |
Ukočenie | Návratová hodnota |
---|---|
úspešné | deskriptor súboru (file descriptor), prirodzené číslo odkazujúce na záznam v tabuľke otvorených súborov, ktorú spravuje operačný systém. Každým volaním open() je vytvorený nový deskriptor, odkaz na záznam. [man 2 open] |
chyba | - 1 |
Výber možných hodnôt pre parameter flags
, rozšírený zoznam v manuáloch
flags - nastaveie režimu | musí obsahovať jednu z nich |
---|---|
O_RDONLY | otvoriť súbor len pre čítanie |
O_WRONLY | otvoriť súbor len pre zápis |
O_RDWR | otvoriť súbor pre zápis aj čítanie |
Aktivita - open - o1-1
Príklad - open - pr1-1
Príklad 1.1
Otvorenie súboru file1.txt
, ktorý sa nachádza v adresári dir1
len na čítanie.
//pr1-1.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
const char* path_to_file= "dir1/file1.txt";
int fd = open(path_to_file, O_RDONLY);
printf("Deskriptor suboru: %d\n\n", fd);
printf("Vypis premennej errno: %d\n",errno);
perror("Vypis funkcie perror pre funkciu open");
return 0;
}
//výstup
Deskriptor suboru: 3
Vypis premennej errno: 0
Vypis funkcie perror pre funkciu open: Success
Poznámka
Súbor s príkladom pr1-1.c
máte v archíve.
Preložiť ho môžete pomocou súboru Makefile
.
//preklad
$ make pr1-1
//spustenie preloženého súboru
$ ./pr1-1
Upozornenie
Ak ste nechtiac upravili súbory, potrebnú štruktúru môžete vytvoriť nasledujúcim postupom.
//príprava
mkdir dir1
echo presmerovany_vystup_do_suboru > dir1/file1.txt
Aktivita - open - u1-1
Úloha 1.1
Pracujte so súborom u1-1.c
, ktorý obsahuje kód z príkladu. Upravte kód tak, aby parameter path
služby open()
bol zadaný z povelového riadka. Pokúste sa otvoriť súbor file2
v režime iba na čítanie. Súbor file2
neexistuje, preto očakávajte chybu. Všimnite si výpis funkcie perror
, ktorý závisí od hodnoty errno.
//u1-1.c
//DOPLNTE KNIŽNICE
int main(int argc, char const *argv[])
{
const char* path_to_file= "dir1/file1.txt";
int fd = open(path_to_file, O_RDONLY);
printf("Deskriptor suboru: %d\n\n", fd);
printf("Vypis premennej errno: %d\n",errno);
perror("Vypis funkcie perror pre funkciu open");
return 0;
}
//preklad
make u1-1
//spustenie programu
./u1-1 file2
//otestovanie riešenia
./tests/u1-1-test.sh
Teória - flags
Hodnota parametra flags
môže byť tvorená kombináciou hodnôt.
flags - nastaveie režimu | ďalšie hodnoty |
---|---|
O_APPEND | doplnenie na koniec pre každý zápis |
O_CREAT | vytvoreie súboru ak neexistuje |
O_TRUNC | skrátenie veľkosti súboru na 0 |
O_EXCL | ukončenie s chybou, ak súbor už existuje |
Hodnoty pre nastavenie práv parametrom mode:
mode - prístupové práva | osmičková hodnota | popis |
---|---|---|
S_IRUSR | 0400 | čítanie pre majiteľa |
S_IWUSR | 0200 | zápis pre majiteľa |
S_IXUSR | 0100 | vykonávanie pre majiteľa |
S_IRWXG | 0070 | čítanie, zápis a vykonávanie pre skupinu |
S_IRWXO | 0007 | čítanie, zápis a vykonávanie pre ostatných |
Právam bude venovaný väčší priestor na nasledujúcom cvičení.
Aktivita - flags - o1-2 o1-3
Aktivita - open - u1-2
Úloha 1.2
Upravte riešenie predchádzajúcej úlohy. Otvorte súbor file2
tak, aby bolo možné z neho čítať aj doň zapisovať. Zabezpečte, aby sa vytvoril v adresári dir1
. Prístupové práva nech majú hodnotu 0777
. Hodnotu parametra flags
je možné vytvoriť kombináciou hodnôt pomocou bitového súčtu operátorom OR. Pracujte so súborom u1-2.c
.
//u1-2.c
//DOPLNTE KNIŽNICE
int main(int argc, char const *argv[])
{
int fd = open(, O_RDONLY);
printf("Deskriptor suboru: %d\n\n", fd);
printf("Vypis premennej errno: %d\n",errno);
perror("Vypis funkcie perror pre funkciu open");
return 0;
}
//možný výstup
Deskriptor suboru: 3
Vypis premennej errno: 0
Vypis funkcie perror pre funkciu open: Success
//preklad
make u1-2
//spustenie programu
./u1-2
//otestovanie riešenia
./tests/u1-2-test.sh
Poznámka
Všimnite si, či sú práva vytvoreného súboru skutočne 0777
. Na prístupové práva pri vytváraní súborov vplýva okrem parametra v open() aj nastavenie masky práv. Podrobnejšie to bude vysvetlené na nasledujúcom cvičení.
Upozornenie
Ak ste nechtiac upravili súbory, potrebnú štruktúru môžete vytvoriť nasledujúcim postupom.
//príprava
mkdir dir1
Krok 2: Čítanie a zápis
Teória - read, write
Po získaní súborového deskriptora je možné pristupovať k obsahu súboru.
Služby jadra read() a write()
read() načíta count
bajtov z kanálu fd
do vyrovnávacej pamäte buf
a vráti počet načítaných bajtov. Vráti 0, keď už predtým dosiahla koniec súboru.
write() zapíše count
bajtov do kanálu fd
z vyrovnávacej pamäte buf
. Vráti počet zapísaných bajtov.
#include <unistd.h>
read(int fd , char * buf , size_t count );
write(int fd , const char * buf , size_t count )
Parametere | Popis |
---|---|
fd | deskriptor súboru |
buf | vyrovnávacia pamäť |
count | počet bajtov |
Ukočenie | Návratová hodnota |
---|---|
úspešné | počet prečítaných / zapísaných bajtov |
chyba | -1 |
Príklad - read - pr2-1
Príklad 2.1
Čítanie zo súboru.
//pr2-1.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int fd1 = open("numbers.txt", O_RDONLY);
char buf[] = "abcdefghijklmn";
int number_read;
number_read = read(fd1, buf, 3);
printf("Počet prečítaných bajtov: %d\n", number_read);
close(fd1);
printf("Buffer po zmene %s\n", buf);
return 0;
}
//výstup
Počet prečítaných bajtov: 3
Buffer po zmene 123defghijklmn
Upozornenie
Ak ste nechtiac upravili súbory, potrebnú štruktúru môžete vytvoriť nasledujúcim postupom.
//príprava
echo 123456789 > numbers.txt
Príklad - write - pr2-2 o2-1
Príklad 2.2
Do súborov, ktoré majú na začiatku rovnaký obsah zapíšeme rôzny počet bajtov.
//obsah súborov na začiatku
$ more file3.txt
123456789
$ more file4.txt
123456789
//pr2-2.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int fd1 = open("file3.txt", O_WRONLY);
int fd2 = open("file4.txt", O_WRONLY);
char buf[] = "abc";
int number_write;
number_write = write(fd1, buf, 3);
printf("Počet zapísaných bajtov: %d\n", number_write);
close(fd1);
number_write = write(fd2, buf, 4);
printf("Počet zapísaných bajtov: %d\n", number_write);
close(fd2);
.
return 0;
}
//výstup
Počet zapísaných bajtov: 3
Počet zapísaných bajtov: 4
//obsah súborov po spustení
$ more file3.txt
abc456789
$ more file4.txt
abc
$ cat file4.txt
abc56789
$ xxd file4.txt
00000000: 6162 6300 3536 3738 390a abc.56789.
Upozornenie
Ak ste nechtiac upravili súbory, potrebnú štruktúru môžete vytvoriť nasledujúcim postupom.
//príprava
echo 123456789 > file3.txt
echo 123456789 > file4.txt
Aktivita - read, write - u2-1 o2-2
Ukazovateľ aktuálnej pozície v súbore je miesto v súbore (konkrétny bajt), na ktorom sa bude vykonávať nasledujúca operácia read()
alebo write()
.
Úloha 2.1
Zo súboru file5.txt
prečítajte prvé dva znaky a hneď ich aj zapíšte do súboru file5.txt
. Parameter flags
nech obsahuje iba hodnotu pre režim (čítanie / zápis / čížanie a zápis). Pracujte so súborom u2-1.c
.
//u2-1.c
#include <stdio.h>
int main(int argc, char **argv)
{
int des;
des=open("file5.txt",O_RDWR);
printf("%ld \n", /*pocet precitanych*/);
printf("%ld \n", /*pocet zapisanych*/);
close(des);
return 0;
}
//otestovanie riešenia
./tests/u2-1-test.sh
Upozornenie
Ak ste nechtiac upravili súbory, potrebnú štruktúru môžete vytvoriť nasledujúcim postupom.
//príprava
echo 123456789 > file5.txt
Prečo boli znaky zapísané na tejto pozícii? Pri čítaní zo súboru sa presunul ukazovateľ aktuálnej pozície.
Aktivita - read, write - u2-2
Úloha 2.2
Upravte program z predchádzajúcej úlohy tak, aby sa čítalo zo súboru file6.txt
a zapisovalo sa na jeho koniec. Pracujte so súborom u2-2.c
.
//obsah súboru file6.txt na začiatku
123456789
//u2-2.c
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int des;
char buf[10];
des=open();
perror("open()");
printf("%ld \n", /*pocet precitanych*/);
perror("");
printf("%ld \n", /*pocet zapisanych*/);
perror("");
close(des);
perror("close);
return 0;
}
//očakávaný obsah súboru file6.txt po spustení
$ more file6.txt
12345678912
//otestovanie riešenia
./tests/u2-2-test.sh
Upozornenie
Ak ste nechtiac upravili súbory, potrebnú štruktúru môžete vytvoriť nasledujúcim postupom.
//príprava
echo -n 123456789 > file6.txt
Aktivita - open, read, write - o2-3
Upozornenie
Ak ste nechtiac upravili súbory, potrebnú štruktúru môžete vytvoriť nasledujúcim postupom.
//príprava
echo -n 123456789 > file7.txt
Krok 3: Zmena pozície kurzoru v súbore
Teória - lseek
Bežný spôsob práce so súborom je sekvenčný (ukazovateľ aktuálnej pozície v súbore sa priebežne zvyšuje). V prípade potreby je možné zo súborov čítať alebo do nich zapisovať na ľubovoľnej pozícii.
Služba jadra lseek()
lseek() zmení pozíciu kurzora v súbore
#include <sys/types.h>
#include <unistd.h>
long lseek (int fd,long offset,int origin);
Parametere | Popis |
---|---|
fd | deskriptor súboru |
offset | posunutie |
origin | pozícia, od ktorej sa posunutie vykoná |
Ukočenie | Návratová hodnota |
---|---|
úspešné | nová pozícia vzhľadom na začiatok súboru |
chyba | -1 |
origin | popis |
---|---|
SEEK_SET | pozícia kurzoru s hodnotou začiatku súboru |
SEEK_CUR | aktuálna pozícia kurzoru v súbore |
SEEK_END | pozícia kurzoru v súbore s hodnotou konca súboru |
Príklad - lseek - pr3-1
Príklad 3.1
Zistenie dĺžky súboru file8.txt
.
//obsah súboru file8.txt pred spustením
$ more file8.txt
abcd
//pr3-1.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int fd = open("file8.txt", O_RDONLY);
long l;
l = lseek(fd, 0L, SEEK_END);
printf("Subor je dlhy %ld bajty.\n", l);
.
close(fd);
return 0;
}
//výstup
Subor je dlhy 4 bajty.
//informácie o súbore
$ stat file8.txt
File: file8.txt
Size: 4 Blocks: 8 IO Block: 4096 obyčajný súbor
//spustenie
./pr3-1
Upozornenie
Ak ste nechtiac upravili súbory, potrebnú štruktúru môžete vytvoriť nasledujúcim postupom.
//príprava
echo -n abcd > file8.txt
Aktivita - lseek - u3-1
Úloha 3.1
V súbore nazov.txt
sa vyskytuje chyba v názve predmetu. Opravte ju pomocou služby jadra lseek()
. Nastavenie pozície nech sa vzťahuje na začiatok súboru. Pracujte so súborom u3-1.c
//obsah súboru nazov.txt na začiatku
Operacne ystemy
//očakávaný obsah súboru nazov.txt
Operacne systemy
//otestovanie riešenia
./tests/u3-1-test.sh
Upozornenie
Ak ste nechtiac upravili súbory, potrebnú štruktúru môžete vytvoriť nasledujúcim postupom.
//príprava
echo "Operacne ystemy" > nazov.txt
echo "Operačné ystémy" > nazov2.txt
Aktivita - lseek - o3-1
Aktivita - lseek - o3-2
Upozornenie
Ak ste nechtiac upravili súbory, potrebnú štruktúru môžete vytvoriť nasledujúcim postupom.
//príprava
echo -n deep thought > lseek1.txt
Aktivita - lseek - o3-3
Upozornenie
Ak ste nechtiac upravili súbory, potrebnú štruktúru môžete vytvoriť nasledujúcim postupom.
//príprava
echo -n 123456789 > lseek2.tx
Krok 4: Riedke súbory
Aktivita - u4-1
V predchádzajúcej úlohe bolo ukázané, že pri pokuse presunúť ukazovateľ pred začiatok súboru nastane chyba. Zistite, aký bude výsledok pri presunutí ukazovateľa za koniec súboru.
Úloha 4.1
Pokúste sa presunúť ukazovateľ za koniec súboru lseek3.txt
. Posunutie nech sa vzťahuje na koniec súboru. Ak je to možné upravte obsah súboru nasledovne.
//súbor na začiatku
$ xxd lseek3.txt
00000000: 3132 3334 1234
//súbor po úprave
$ xxd lseek3.txt
00000000: 3132 3334 0000 0000 0000 0000 0000 6162 1234..........ab
Pracujte v súbore u4-1.c
.
//otestovanie riešenia
./tests/u4-1-test.sh
Upozornenie
Ak ste nechtiac upravili súbory, potrebnú štruktúru môžete vytvoriť nasledujúcim postupom.
//príprava
echo -n 1234 > lseek3.txt
Súbor po úprave obsahuje oblasť s nulovými bajtmi.
Príklad - pr4-1
Úlohu bolo možné vyriešiť aj bez použitia služby lseek.
Príklad 4.1
Možnosť zapísania prázdnych bajtov.
//pr4-1.c
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{
//vytvorte subor abc...123 pomocou write
int fd1= open("outfile2", O_WRONLY | O_CREAT, 0770);
char buffer[9] = {'a', 'b', 'c', 0, 0, '\0', '1', '2', '3'};
write(fd1, &buffer, 9);
close(fd1);
return 0;
}
$ ls -al outfile2
-rwxrwx--- 1 sofia sofia 9 mar 31 19:58 outfile2
$ xxd outfile2
00000000: 6162 6300 0000 3132 33 abc...123
V súbore outfile2
je oblasť s nulovými bajtmi malá.
Príklad - pr4-2
Pozrite sa na prípad, keď súbor obsahuje blok s nulovými bajtmi.
Priklad 4.2 ytvorenie súboru danej veľkosti. Všimnite si zmenu v počte blokov.
$ truncate -s 10 sparse2.txt
$ xxd sparse2.txt
00000000: 0000 0000 0000 0000 0000 ..........
$ stat sparse2.txt
File: sparse2.txt
Size: 10 Blocks: 0 IO Block: 4096 obyčajný súbor
Po zapísaní znaku do súboru sparse2.txt.
$ xxd sparse2.txt
00000000: 6100 0000 0000 0000 0000 a.........
$ stat sparse2.txt
File: sparse2.txt
Size: 10 Blocks: 8 IO Block: 4096 obyčajný súbor
$ stat --printf='File: %n\nSize: %s (in bytes)\nBlocks allocated: %b\nSize of block: %B (in bytes)\n' sparse2.txt
File: sparse2.txt
Size: 10 (in bytes)
Blocks allocated: 8
Size of block: 512 (in bytes)
Súbor, ktorý obsahuje oblasť s nulovými bajtmi ("dierami") nazávame riedky súbor (sparse file). Pre úsporne ukladanie takýchto súborov, sa na disk zapíšu iba informácie o polohe a dĺžke dier (metaúdaje) a samotné diery sa neukladajú.