ndeksnyj uzel,
pasport), chto i ishodnyj fajl. |to prosto al'ternativnoe imya fajla, uchityvaemoe
v pole di_nlink v I-node.
____________________
|- BSD - semejstvo UNIX-ov iz University of California, Berkley. Berkley Software
Distribution.
A. Bogatyrev, 1992-95 - 145 - Si v UNIX
symbolic link
- sozdaetsya vyzovom symlink. |to otdel'nyj samostoyatel'nyj fajl, s sobstvennym
I-node. Pravda, kody dostupa k etomu fajlu ne igrayut nikakoj roli; znachimy
tol'ko kody dostupa ukazuemogo fajla.
4.7. Napishite programmu, kotoraya nahodit v fajle simvol @ i vydaet fajl s etogo
mesta dvazhdy. Ukazanie: dlya zapominaniya pozicii v fajle ispol'zujte vyzov lseek() -
pozicionirovanie ukazatelya chteniya/zapisi:
long offset, lseek();
...
/* Uznat' tekushchuyu poziciyu chteniya/zapisi:
* sdvig na 0 ot tekushchej pozicii. lseek vernet novuyu
* poziciyu ukazatelya (v bajtah ot nachala fajla). */
offset = lseek(fd, 0L, 1); /* ftell(fp) */
A dlya vozvrata v etu tochku:
lseek(fd, offset, 0); /* fseek(fp, offset, 0) */
Po povodu lseek nado pomnit' takie veshchi:
- lseek(fd, offset, whence) ustanavlivaet ukazatel' chteniya/zapisi na rasstoyanie
offset bajt
pri whence:
0 ot nachala fajla RWptr = offset;
1 ot tekushchej pozicii RWptr += offset;
2 ot konca fajla RWptr = dlina_fajla + offset;
|ti znacheniya whence mozhno oboznachat' imenami:
#include <stdio.h>
0 eto SEEK_SET
1 eto SEEK_CUR
2 eto SEEK_END
- Ustanovka ukazatelya chteniya/zapisi - eto virtual'naya operaciya, t.e. real'nogo
podvoda magnitnyh golovok i voobshche obrashcheniya k disku ona ne vyzyvaet. Real'noe
dvizhenie golovok k nuzhnomu mestu diska proizojdet tol'ko pri operaciyah
chteniya/zapisi read()/write(). Poetomu lseek() - deshevaya operaciya.
- lseek() vozvrashchaet novuyu poziciyu ukazatelya chteniya/zapisi RWptr otnositel'no
nachala fajla (long smeshchenie v bajtah). Pomnite, chto esli vy ispol'zuete eto zna-
chenie, to vy dolzhny predvaritel'no opisat' lseek kak funkciyu, vozvrashchayushchuyu dlin-
noe celoe: long lseek();
- Argument offset dolzhen imet' tip long (ne oshibites'!).
- Esli postavit' ukazatel' za konec fajla (eto dopustimo!), to operaciya zapisi
write() snachala zapolnit bajtom '\0' vse prostranstvo ot konca fajla do pozicii
ukazatelya; operaciya read() pri popytke chteniya iz-za konca fajla vernet "prochi-
tano 0 bajt". Popytka postavit' ukazatel' pered nachalom fajla vyzovet oshibku.
- Vyzov lseek() neprimenim k pipe i FIFO-fajlam, poetomu popytka sdvinut'sya na 0
bajt vydast oshibku:
/* eto standartnaya funkciya */
int isapipe(int fd){
extern errno;
return (lseek(fd, 0L, SEEK_CUR) < 0 && errno == ESPIPE);
}
vydaet "istinu", esli fd - deskriptor "truby"(pipe).
A. Bogatyrev, 1992-95 - 146 - Si v UNIX
4.8. Kakov budet effekt sleduyushchej programmy?
int fd = creat("aFile", 0644); /* creat sozdaet fajl
otkrytyj na zapis', s dostupom rw-r--r-- */
write(fd, "begin", 5 );
lseek(fd, 1024L * 1000, 0);
write(fd, "end", 3 );
close(fd);
Napomnim, chto pri zapisi v fajl, ego dlina avtomaticheski uvelichivaetsya, kogda my
zapisyvaem informaciyu za prezhnim koncom fajla. |to vyzyvaet otvedenie mesta na diske
dlya hraneniya novyh dannyh (porciyami, nazyvaemymi blokami - razmerom ot 1/2 do 8 Kb v
raznyh versiyah). Takim obrazom, razmer fajla ogranichen tol'ko nalichiem svobodnyh
blokov na diske.
V nashem primere poluchitsya fajl dlinoj 1024003 bajta. Budet li on zanimat' na
diske 1001 blok (po 1 Kb)?
V sisteme UNIX - net! Vot koe-chto pro mehaniku vydeleniya blokov:
- Bloki raspolagayutsya na diske ne obyazatel'no podryad - u kazhdogo fajla est' speci-
al'nym obrazom organizovannaya tablica adresov ego blokov.
- Poslednij blok fajla mozhet byt' zanyat ne celikom (esli dlina fajla ne kratna
razmeru bloka), tem ne menee chislo blokov u fajla vsegda celoe (krome semejstva
BSD, gde blok mozhet delit'sya na fragmenty, prinadlezhashchie raznym fajlam). Opera-
cionnaya sistema v kazhdyj moment vremeni znaet dlinu fajla s tochnost'yu do odnogo
bajta i ne pozvolyaet nam "zaglyadyvat'" v ostatok bloka, poka pri svoem "roste"
fajl ne zajmet eti bajty.
- Blok na diske fizicheski vydelyaetsya lish' posle operacii zapisi v etot blok.
V nashem primere: pri sozdanii fajla ego razmer 0, i emu vydeleno 0 blokov. Pri
pervoj zapisi fajlu budet vydelen odin blok (logicheskij blok nomer 0 dlya fajla) i v
ego nachalo zapishetsya "begin". Dlina fajla stanet ravna 5 (ostatok bloka - 1019 bajt
- ne ispol'zuetsya i fajlu logicheski ne prinadlezhit!). Zatem lseek postavit ukazatel'
zapisi daleko za konec fajla i write zapishet v 1000-yj blok slovo "end". 1000-yj blok
budet vydelen na diske. V etot moment u fajla "vozniknut" i vse promezhutochnye bloki
1..999. Odnako oni budut tol'ko "chislit'sya za fajlom", no na diske otvedeny ne budut
(v tablice blokov fajla eto oboznachaetsya adresom 0)! Pri chtenii iz nih budut
chitat'sya bajty '\0'. |to tak nazyvaemaya "dyrka" v fajle. Fajl imeet razmer 1024003
bajta, no na diske zanimaet vsego 2 bloka (na samom dele chut' bol'she, t.k. chast'
tablicy blokov fajla tozhe nahoditsya v special'nyh blokah fajla). Blok iz "dyrki"
stanet real'nym, esli v nego chto-nibud' zapisat'.
Bud'te gotovy k tomu, chto "razmer fajla" (kotoryj, kstati, mozhno uznat' sistem-
nym vyzovom stat) - eto v UNIX ne to zhe samoe, chto "mesto, zanimaemoe fajlom na
diske".
4.9. Najdite oshibki:
FILE *fp;
...
fp = open( "fajl", "r" ); /* otkryt' */
close(fp); /* zakryt' */
Otvet: ispol'zuetsya sistemnyj vyzov open() vmesto funkcii fopen(); a takzhe close
vmesto fclose, a ih formaty (i rezul'tat) razlichayutsya! Sleduet chetko razlichat' dve
sushchestvuyushchie v Si modeli obmena s fajlami: cherez sistemnye vyzovy: open, creat,
close, read, write, lseek; i cherez biblioteku buferizovannogo obmena stdio: fopen,
fclose, fread, fwrite, fseek, getchar, putchar, printf, i.t.d. V pervoj iz nih obra-
shchenie k fajlu proishodit po celomu fd - deskriptoru fajla, a vo vtorom - po ukazatelyu
FILE *fp - ukazatelyu na fajl. |to parallel'nye mehanizmy (po svoim vozmozhnostyam),
hotya vtoroj yavlyaetsya prosto nadstrojkoj nad pervym. Tem ne menee, luchshe ih ne smeshi-
vat'.
A. Bogatyrev, 1992-95 - 147 - Si v UNIX
4.10. Dostup k disku (chtenie/zapis') gorazdo (na neskol'ko poryadkov) medlennee, chem
dostup k dannym v operativnoj pamyati. Krome togo, esli my chitaem ili zapisyvaem fajl
pri pomoshchi sistemnyh vyzovov malen'kimi porciyami (po 1-10 simvolov)
char c;
while( read(0, &c, 1)) ... ; /* 0 - standartnyj vvod */
to my proigryvaem eshche v odnom: kazhdyj sistemnyj vyzov - eto obrashchenie k yadru operaci-
onnoj sistemy. Pri kazhdom takom obrashchenii proishodit dovol'no bol'shaya dopolnitel'naya
rabota (smotri glavu "Vzaimodejstvie s UNIX"). Pri etom nakladnye rashody na takoe
posimvol'noe chtenie fajla mogut znachitel'no prevysit' poleznuyu rabotu.
Eshche odnoj problemoj yavlyaetsya to, chto sistemnye vyzovy rabotayut s fajlom kak s
nestrukturirovannym massivom bajt; togda kak cheloveku chasto udobnee predstavlyat', chto
fajl podelen na stroki, soderzhashchie chitabel'nyj tekst, sostoyashchij lish' iz obychnyh
pechatnyh simvolov (tekstovyj fajl).
Dlya resheniya etih dvuh problem byla postroena special'naya biblioteka funkcij,
nazvannaya stdio - "standartnaya biblioteka vvoda/vyvoda" (standard input/output
library). Ona yavlyaetsya chast'yu biblioteki /lib/libc.a i predstavlyaet soboj nadstrojku
nad sistemnymi vyzovami (t.k. v konce koncov vse ee funkcii vremya ot vremeni obrashcha-
yutsya k sisteme, no gorazdo rezhe, chem esli ispol'zovat' sisvyzovy neposredstvenno).
Nebezyzvestnaya direktiva #include <stdio.h> vklyuchaet v nashu programmu fajl s ob®yavle-
niem formatov dannyh i konstant, ispol'zuemyh etoj bibliotekoj.
Biblioteku stdio mozhno nazvat' bibliotekoj buferizovannogo obmena, a takzhe bib-
liotekoj raboty s tekstovymi fajlami (t.e. imeyushchimi razdelenie na stroki), poskol'ku
dlya optimizacii obmenov s diskom (dlya umen'sheniya chisla obrashchenij k nemu i tem samym
sokrashcheniya chisla sistemnyh vyzovov) eta biblioteka vvodit buferizaciyu, a takzhe pre-
dostavlyaet neskol'ko funkcij dlya raboty so strochno-organizovannymi fajlami.
Svyaz' s fajlom v etoj modeli obmena osushchestvlyaetsya uzhe ne pri pomoshchi celogo
chisla - deskriptora fajla (file descriptor), a pri pomoshchi adresa "svyaznoj" struktury
FILE. Ukazatel' na takuyu strukturu uslovno nazyvayut ukazatelem na fajl (file
pointer)|-. Struktura FILE soderzhit v sebe:
- deskriptor fd fajla dlya obrashcheniya k sistemnym vyzovam;
- ukazatel' na bufer, razmeshchennyj v pamyati programmy;
- ukazatel' na tekushchee mesto v bufere, otkuda nado vydat' ili kuda zapisat' oche-
rednoj simvol; etot ukazatel' prodvigaetsya pri kazhdom vyzove getc ili putc;
- schetchik ostavshihsya v bufere simvolov (pri chtenii) ili svobodnogo mesta (pri
zapisi);
- rezhimy otkrytiya fajla (chtenie/zapis'/chtenie+zapis') i tekushchee sostoyanie fajla.
Odno iz sostoyanij - pri chtenii fajla byl dostignut ego konec|=;
- sposob buferizacii;
Predusmotreno neskol'ko standartnyh struktur FILE, ukazateli na kotorye nazyvayutsya
stdin, stdout i stderr i svyazany s deskriptorami 0, 1, 2 sootvetstvenno (standartnyj
vvod, standartnyj vyvod, standartnyj vyvod oshibok). Napomnim, chto eti kanaly otkryty
neyavno (avtomaticheski) i, esli ne perenapravleny, svyazany s vvodom s klaviatury i
vyvodom na terminal.
Bufer v operativnoj pamyati nashej programmy sozdaetsya (funkciej malloc) pri otk-
rytii fajla pri pomoshchi funkcii fopen(). Posle otkrytiya fajla vse operacii obmena s
fajlom proishodyat ne po 1 bajtu, a bol'shimi porciyami razmerom s bufer - obychno po 512
bajt (konstanta BUFSIZ).
Pri chtenii simvola
int c; FILE *fp = ... ;
c = getc(fp);
____________________
|- |to ne ta "svyazuyushchaya" struktura file v yadre, pro kotoruyu shla rech' vyshe, a ESHCHE
odna - v pamyati samoj programmy.
|= Proverit' eto sostoyanie pozvolyaet makros feof(fp); on istinen, esli konec byl
dostignut, lozhen - esli eshche net.
A. Bogatyrev, 1992-95 - 148 - Si v UNIX
v bufer schityvaetsya read-om iz fajla porciya informacii, i getc vydaet ee pervyj bajt.
Pri posleduyushchih vyzovah getc vydayutsya sleduyushchie bajty iz bufera, a obrashchenij k disku
uzhe ne proishodit! Lish' kogda bufer budet ischerpan - proizojdet ocherednoe chtenie s
diska. Takim obrazom, informaciya chitaetsya iz fajla s operezheniem, zaranee napolnyaya
bufer; a po trebovaniyu vydaetsya uzhe iz bufera. Esli my chitaem 1024 bajta iz fajla
pri pomoshchi getc(), to my 1024 raza vyzyvaem etu funkciyu, no vsego 2 raza sistemnyj
vyzov read - dlya chteniya dvuh porcij informacii iz fajla, kazhdaya - po 512 bajt.
Pri zapisi
char c; FILE *fp = ... ;
putc(c, fp);
vyvodimye simvoly nakaplivayutsya v bufere. Tol'ko kogda v nem okazhetsya bol'shaya porciya
informacii, ona za odno obrashchenie write zapisyvaetsya na disk. Bufer zapisi "vytalki-
vaetsya" v fajl v takih sluchayah:
- bufer zapolnen (soderzhit BUFSIZ simvolov).
- pri zakrytii fajla (fclose ili exit |-|-).
- pri vyzove funkcii fflush (sm. nizhe).
- v special'nom rezhime - posle pomeshcheniya v bufer simvola '\n' (sm. nizhe).
- v nekotoryh versiyah - pered lyuboj operaciej chteniya iz kanala stdin (naprimer,
pri vyzove gets), pri uslovii, chto stdout buferizovan postrochno (rezhim _IOLBF,
smotri nizhe), chto po-umolchaniyu tak i est'.
Privedem uproshchennuyu shemu, poyasnyayushchuyu vzaimootnosheniya osnovnyh funkcij i makrosov iz
stdio (kto kogo vyzyvaet). Dalee s oznachaet stroku, c - simvol, fp - ukazatel' na
strukturu FILE |=|=. Funkcii, rabotayushchie so strokami, v cikle vyzyvayut posimvol'nye
operacii. Obratite vnimanie, chto v konce koncov vse funkcii obrashchayutsya k sistemnym
vyzovam read i write, osushchestvlyayushchim vvod/vyvod nizkogo urovnya.
Sistemnye vyzovy dalee oboznacheny zhirno, makrosy - kursivom.
Otkryt' fajl, sozdat' bufer:
#include <stdio.h>
FILE *fp = fopen(char *name, char *rwmode);
| vyzyvaet
V
int fd = open (char *name, int irwmode);
Esli otkryvaem na zapis' i fajl ne sushchestvuet (fd < 0),
to sozdat' fajl vyzovom:
fd = creat(char *name, int accessmode);
fd budet otkryt dlya zapisi v fajl.
Po umolchaniyu fopen() ispol'zuet dlya creat kody dostupa accessmode ravnye 0666 (rw-
rw-rw-).
____________________
|-|- Pri vypolnenii vyzova zaversheniya programmy exit(); vse otkrytye fajly avtomati-
cheski zakryvayutsya.
|=|= Oboznacheniya fd dlya deskriptorov i fp dlya ukazatelej na fajl prizhilis' i ih sle-
duet priderzhivat'sya. Esli peremennaya dolzhna imet' bolee mnemonichnoe imya - sleduet
pisat' tak: fp_output, fd_input (a ne prosto fin, fout).
A. Bogatyrev, 1992-95 - 149 - Si v UNIX
Sootvetstvie argumentov fopen i open:
rwmode irwmode
-------------------------
"r" O_RDONLY
"w" O_WRONLY|O_CREAT |O_TRUNC
"r+" O_RDWR
"w+" O_RDWR |O_CREAT |O_TRUNC
"a" O_WRONLY|O_CREAT |O_APPEND
"a+" O_RDWR |O_CREAT |O_APPEND
Dlya r, r+ fajl uzhe dolzhen sushchestvovat', v ostal'nyh sluchayah fajl sozdaetsya, esli ego
ne bylo.
Esli fopen() ne smog otkryt' (ili sozdat') fajl, on vozvrashchaet znachenie NULL:
if((fp = fopen(name, rwmode)) == NULL){ ...neudacha... }
Itak, shema:
printf(fmt,...)--->--,----fprintf(fp,fmt,...)->--*
fp=stdout |
fputs(s,fp)--------->--|
puts(s)----------->-------putchar(c)-----,---->--|
fp=stdout |
fwrite(array,size,count,fp)->--|
|
YAdro OS putc(c,fp)
------------------* |
|fajlovaya---<--write(fd,s,len)------------<----BUFER
|sistema---->---read(fd,s,len)-* _flsbuf(c,fp)
| | ! |
|sistemnye bufera ! |
| | ! V ungetc(c,fp)
|drajver ustr-va ! | |
|(disk, terminal) ! | _filbuf(fp) |
| | ! *--------->-----BUFER<-*
|ustrojstvo ! |
------------------* c=getc(fp)
|
rdcount=fread(array,size,count,fp)--<--|
gets(s)-------<---------c=getchar()------,----<--|
fp=stdout |
|
fgets(sbuf,buflen,fp)-<--|
scanf(fmt,.../*uk-li*/)--<-,--fscanf(fp,fmt,...)-*
fp=stdin
Zakryt' fajl, osvobodit' pamyat' vydelennuyu pod bufer:
fclose(fp) ---> close(fd);
I chut' v storone - funkciya pozicionirovaniya:
fseek(fp,long_off,whence) ---> lseek(fd,long_off,whence);
Funkcii _flsbuf i _filbuf - vnutrennie dlya stdio, oni kak raz sbrasyvayut bufer v fajl
libo chitayut novyj bufer iz fajla.
Po ukazatelyu fp mozhno uznat' deskriptor fajla:
int fd = fileno(fp);
|to makroopredelenie prosto vydaet pole iz struktury FILE. Obratno, esli my otkryli
A. Bogatyrev, 1992-95 - 150 - Si v UNIX
fajl open-om, my mozhem vvesti buferizaciyu etogo kanala:
int fd = open(name, O_RDONLY); /* ili creat() */
...
FILE *fp = fdopen(fd, "r");
(zdes' nado vnov' ukazat' KAK my otkryvaem fajl, chto dolzhno sootvetstvovat' rezhimu
otkrytiya open-om). Teper' mozhno rabotat' s fajlom cherez fp, a ne fd.
V prilozhenii imeetsya tekst, soderzhashchij uproshchennuyu realizaciyu glavnyh funkcij iz
biblioteki stdio.
4.11. Funkciya ungetc(c,fp) "vozvrashchaet" prochitannyj bajt v fajl. Na samom dele bajt
vozvrashchaetsya v bufer, poetomu eta operaciya neprimenima k nebuferizovannym kanalam.
Vozvrat sootvetstvuet sdvigu ukazatelya chteniya iz bufera (kotoryj uvelichivaetsya pri
getc()) na 1 poziciyu nazad. Vernut' mozhno tol'ko odin simvol podryad (t.e. pered sle-
duyushchim ungetc-om dolzhen byt' hot' odin getc), poskol'ku v protivnom sluchae mozhno
sdvinut' ukazatel' za nachalo bufera i, zapisyvaya tuda simvol c, razrushit' pamyat'
programmy.
while((c = getchar()) != '+' );
/* Prochli '+' */ ungetc(c ,stdin);
/* A mozhno zamenit' etot simvol na drugoj! */
c = getchar(); /* snova prochtet '+' */
4.12. Ochen' chasto delayut oshibku v funkcii fputc, putaya poryadok ee argumentov. Tak
nichego ne stoit napisat':
FILE *fp = ......;
fputc( fp, '\n' );
Zapomnite navsegda!
int fputc( int c, FILE *fp );
ukazatel' fajla idet vtorym! Sushchestvuet takzhe makroopredelenie
putc( c, fp );
Ono vedet sebya kak i funkciya fputc, no ne mozhet byt' peredano v kachestve argumenta v
funkciyu:
#include <stdio.h>
putNtimes( fp, c, n, f )
FILE *fp; int c; int n; int (*f)();
{ while( n > 0 ){ (*f)( c, fp ); n--; }}
vozmozhen vyzov
putNtimes( fp, 'a', 3, fputc );
no nedopustimo
putNtimes( fp, 'a', 3, putc );
Tem ne menee vsegda, gde vozmozhno, sleduet pol'zovat'sya makrosom - on rabotaet byst-
ree. Analogichno, est' funkciya fgetc(fp) i makros getc(fp).
Otmetim eshche, chto putchar i getchar eto tozhe vsego lish' makrosy
#define putchar(c) putc((c), stdout)
#define getchar() getc(stdin)
A. Bogatyrev, 1992-95 - 151 - Si v UNIX
4.13. Izvestnaya vam funkciya printf takzhe yavlyaetsya chast'yu biblioteki stdio. Ona vho-
dit v semejstvo funkcij:
FILE *fp; char bf[256];
fprintf(fp, fmt, ... );
printf( fmt, ... );
sprintf(bf, fmt, ... );
Pervaya iz funkcij formatiruet svoi argumenty v sootvetstvii s formatom, zadannym
strokoj fmt (ona soderzhit formaty v vide %-ov) i zapisyvaet stroku-rezul'tat posim-
vol'no (vyzyvaya putc) v fajl fp. Vtoraya - eto vsego-navsego fprintf s kanalom fp
ravnym stdout. Tretyaya vydaet sformatirovannuyu stroku ne v fajl, a zapisyvaet ee v
massiv bf. V konce stroki sprintf dobavlyaet nulevoj bajt '\0' - priznak konca.
Dlya chteniya dannyh po formatu ispol'zuyutsya funkcii semejstva
fscanf(fp, fmt, /* adresa arg-tov */...);
scanf( fmt, ... );
sscanf(bf, fmt, ... );
Funkcii fprintf i fscanf yavlyayutsya naibolee moshchnym sredstvom raboty s tekstovymi faj-
lami (soderzhashchimi izobrazhenie dannyh v vide pechatnyh simvolov).
4.14. Tekstovye fajly (imeyushchie strochnuyu organizaciyu) hranyatsya na diske kak linejnye
massivy bajt. Dlya razdeleniya strok v nih ispol'zuetsya simvol '\n'. Tak, naprimer,
tekst
str1
strk2
knc
hranitsya kak massiv
s t r 1 \n s t r k 2 \n k n c dlina=14 bajt
!
ukazatel' chteniya/zapisi (read/write pointer RWptr)
(rasstoyanie v bajtah ot nachala fajla)
Pri vyvode na ekran displeya simvol \n preobrazuetsya drajverom terminalov v posledova-
tel'nost' \r\n, kotoraya vozvrashchaet kursor v nachalo stroki ('\r') i opuskaet kursor na
stroku vniz ('\n'), to est' kursor perehodit v nachalo sleduyushchej stroki.
V MS DOS stroki v fajle na diske razdelyayutsya dvumya simvolami \r\n i pri vyvode
na ekran nikakih preobrazovanij ne delaetsya|-. Zato bibliotechnye funkcii yazyka Si
preobrazuyut etu posledovatel'nost' pri chtenii iz fajla v \n, a pri zapisi v fajl
prevrashchayut \n v \r\n, poskol'ku v Si schitaetsya, chto stroki razdelyayutsya tol'ko \n. Dlya
raboty s fajlom bez takih preobrazovanij, ego nado otkryvat' kak "binarnyj":
FILE *fp = fopen( imya, "rb" ); /* b - binary */
int fd = open ( imya, O_RDONLY | O_BINARY );
____________________
|- Upravlyayushchie simvoly imeyut sleduyushchie znacheniya:
'\n' - '\012' (10) line feed
'\r' - '\015' (13) carriage return
'\t' - '\011' (9) tab
'\b' - '\010' (8) backspace
'\f' - '\014' (12) form feed
'\a' - '\007' (7) audio bell (alert)
'\0' - 0. null byte
A. Bogatyrev, 1992-95 - 152 - Si v UNIX
Vse netekstovye fajly v MS DOS nado otkryvat' imenno tak, inache mogut proizojti raz-
nye nepriyatnosti. Naprimer, esli my programmoj kopiruem netekstovyj fajl v tekstovom
rezhime, to odinochnyj simvol \n budet schitan v programmu kak \n, no zapisan v novyj
fajl kak para \r\n. Poetomu novyj fajl budet otlichat'sya ot originala (chto dlya fajlov
s dannymi i programm sovershenno nedopustimo!).
Zadanie: napishite programmu podscheta strok i simvolov v fajle. Ukazanie: nado
podschitat' chislo simvolov '\n' v fajle i uchest', chto poslednyaya stroka fajla mozhet ne
imet' etogo simvola na konce. Poetomu esli poslednij simvol fajla (tot, kotoryj vy
prochitaete samym poslednim) ne est' '\n', to dobav'te k schetchiku strok 1.
4.15. Napishite programmu podscheta kolichestva vhozhdenij kazhdogo iz simvolov alfavita
v fajl i pechatayushchuyu rezul'tat v vide tablicy v 4 kolonki. (Ukazanie: zavedite massiv
iz 256 schetchikov. Dlya bol'shih fajlov schetchiki dolzhny byt' tipa long).
4.16. Pochemu vvodimyj pri pomoshchi funkcij getchar() i getc(fp) simvol dolzhen opisy-
vat'sya tipom int a ne char?
Otvet: funkciya getchar() soobshchaet o konce fajla tem, chto vozvrashchaet znachenie EOF
(end of file), ravnoe celomu chislu (-1). |to NE simvol kodirovki ASCII, poskol'ku
getchar() mozhet prochest' iz fajla lyuboj simvol kodirovki (kodirovka soderzhit simvoly
s kodami 0...255), a special'nyj priznak ne dolzhen sovpadat' ni s odnim iz hranimyh v
fajle simvolov. Poetomu dlya ego hraneniya trebuetsya bol'she odnogo bajta (nuzhen hotya
by eshche 1 bit). Proverka na konec fajla v programme obychno vyglyadit tak:
...
while((ch = getchar()) != EOF ){
putchar(ch);
...
}
- Pust' ch imeet tip unsigned char. Togda ch vsegda lezhit v intervale 0...255 i
NIKOGDA ne budet ravno (-1). Dazhe esli getchar() vernet takoe znachenie, ono
budet privedeno k tipu unsigned char obrubaniem i stanet ravnym 255. Pri srav-
nenii s celym (-1) ono rasshiritsya v int dobavleniem nulej sleva i stanet ravno
255. Takim obrazom, nasha programma nikogda ne zavershitsya, t.k. vmesto priznaka
konca fajla ona budet chitat' simvol s kodom 255 (255 != -1).
- Pust' ch imeet tip signed char. Togda pered sravneniem s celym chislom EOF bajt
ch budet priveden k tipu signed int pri pomoshchi rasshireniya znakovogo bita (7-
ogo). Esli getchar vernet znachenie (-1), to ono budet snachala v prisvaivanii
znacheniya bajtu ch obrubleno do tipa char: 255; no v sravnenii s EOF znachenie 255
budet privedeno k tipu int i poluchitsya (-1). Takim obrazom, istinnyj konec fajla
budet obnaruzhen. No teper', esli iz fajla budet prochitan nastoyashchij simvol s
kodom 255, on budet priveden v sravnenii k celomu znacheniyu (-1) i budet takzhe
vosprinyat kak konec fajla. Takim obrazom, esli v nashem fajle okazhetsya simvol s
kodom 255, to programma vosprimet ego kak fal'shivyj konec fajla i ostavit ves'
ostatok fajla neobrabotannym (a v netekstovyh fajlah takie simvoly - ne red-
kost').
- Pust' ch imeet tip int ili unsigned int (bol'she 8 bit). Togda vse korrektno.
Otmetim, chto v UNIX priznak konca fajla v samom fajle fizicheski NE HRANITSYA. Sistema
v lyuboj moment vremeni znaet dlinu fajla s tochnost'yu do odnogo bajta; priznak EOF
vyrabatyvaetsya standartnymi funkciyami togda, kogda obnaruzhivaetsya, chto ukazatel' chte-
niya dostig konca fajla (to est' poziciya chteniya stala ravnoj dline fajla - poslednij
bajt uzhe prochitan).
V MS DOS zhe v tekstovyh fajlah priznak konca (EOF) hranitsya yavno i oboznachaetsya
simvolom CTRL/Z. Poetomu, esli programmnym putem zapisat' kuda-nibud' v seredinu
fajla simvol CTRL/Z, to nekotorye programmy perestanut "videt'" ostatok fajla posle
etogo simvola!
Nakonec otmetim, chto raznye funkcii pri dostizhenii konca fajla vydayut raznye
znacheniya: scanf, fscanf, fgetc, getc, getchar vydayut EOF, read - vydaet 0, a gets,
fgets - NULL.
A. Bogatyrev, 1992-95 - 153 - Si v UNIX
4.17. Napishite programmu, kotoraya zaprashivaet vashe imya i privetstvuet vas. Dlya vvoda
imeni ispol'zujte standartnye bibliotechnye funkcii
gets(s);
fgets(s,slen,fp);
V chem raznica?
Otvet: funkciya gets() chitaet stroku (zavershayushchuyusya '\n') iz kanala fp==stdin.
Ona ne kontroliruet dlinu bufera, v kotoruyu schityvaetsya stroka, poetomu esli stroka
okazhetsya slishkom dlinnoj - vasha programma povredit svoyu pamyat' (i avarijno zaver-
shitsya). Edinstvennyj vozmozhnyj sovet - delajte bufer dostatochno bol'shim (ochen' tuman-
noe ponyatie!), chtoby vmestit' maksimal'no vozmozhnuyu (dlinnuyu) stroku.
Funkciya fgets() kontroliruet dlinu stroki: esli stroka na vhode okazhetsya dlin-
nee, chem slen simvolov, to ostatok stroki ne budet prochitan v bufer s, a budet ostav-
len "na potom". Sleduyushchij vyzov fgets prochitaet etot sohranennyj ostatok. Krome togo
fgets, v otlichie ot gets, ne obrubaet simvol '\n' na konce stroki, chto dostavlyaet nam
dopolnitel'nye hlopoty po ego unichtozheniyu, poskol'ku v Si "normal'nye" stroki zaver-
shayutsya prosto '\0', a ne "\n\0".
char buffer[512]; FILE *fp = ... ; int len;
...
while(fgets(buffer, sizeof buffer, fp)){
if((len = strlen(buffer)) && buffer[len-1] == '\n')
/* @ */ buffer[--len] = '\0';
printf("%s\n", buffer);
}
Zdes' len - dlina stroki. Esli by my vybrosili operator, pomechennyj '@', to printf
pechatal by tekst cherez stroku, poskol'ku vydaval by kod '\n' dvazhdy - iz stroki
buffer i iz formata "%s\n".
Esli v fajle bol'she net strok (fajl dochitan do konca), to funkcii gets i fgets
vozvrashchayut znachenie NULL. Obratite vnimanie, chto NULL, a ne EOF. Poka fajl ne dochi-
tan, eti funkcii vozvrashchayut svoj pervyj argument - adres bufera, v kotoryj byla zapi-
sana ocherednaya stroka fajla.
Fragment dlya obrubaniya simvola perevoda stroki mozhet vyglyadet' eshche tak:
#include <stdio.h>
#include <string.h>
char buffer[512]; FILE *fp = ... ;
...
while(fgets(buffer, sizeof buffer, fp) != NULL){
char *sptr;
if(sptr = strchr(buffer, '\n'))
*sptr = '\0';
printf("%s\n", buffer);
}
4.18. V chem otlichie puts(s); i fputs(s,fp); ?
Otvet: puts vydaet stroku s v kanal stdout. Pri etom puts vydaet snachala stroku
s, a zatem - dopolnitel'no - simvol perevoda stroki '\n'. Funkciya zhe fputs simvol
perevoda stroki ne dobavlyaet. Uproshchenno:
fputs(s, fp) char *s; FILE *fp;
{ while(*s) putc(*s++, fp); }
puts(s) char *s;
{ fputs(s, stdout); putchar('\n'); }
A. Bogatyrev, 1992-95 - 154 - Si v UNIX
4.19. Najdite oshibki v programme:
#include <stdio.h>
main() {
int fp;
int i;
char str[20];
fp = fopen("fajl");
fgets(stdin, str, sizeof str);
for( i = 0; i < 40; i++ );
fputs(fp, "Tekst, vyvodimyj v fajl:%s",str );
fclose("fajl");
}
Moral': nado byt' vnimatel'nee k formatu vyzova i smyslu bibliotechnyh funkcij.
4.20. Napishite programmu, kotoraya raspechatyvaet samuyu dlinnuyu stroku iz fajla vvoda
i ee dlinu.
4.21. Napishite programmu, kotoraya vydaet n-uyu stroku fajla. Nomer stroki i imya
fajla zadayutsya kak argumenty main().
4.22. Napishite programmu
slice -sKakoj +skol'ko fajl
kotoraya vydaet skol'ko strok fajla fajl, nachinaya so stroki nomer sKakoj (numeraciya
strok s edinicy).
#include <stdio.h>
#include <ctype.h>
long line, count, nline, ncount; /* nuli */
char buf[512];
void main(int argc, char **argv){
char c; FILE *fp;
argc--; argv++;
/* Razbor klyuchej */
while((c = **argv) == '-' || c == '+'){
long atol(), val; char *s = &(*argv)[1];
if( isdigit(*s)){
val = atol(s);
if(c == '-') nline = val;
else ncount = val;
} else fprintf(stderr,"Neizvestnyj klyuch %s\n", s-1);
argc--; ++argv;
}
if( !*argv ) fp = stdin;
else if((fp = fopen(*argv, "r")) == NULL){
fprintf(stderr, "Ne mogu chitat' %s\n", *argv);
exit(1);
}
for(line=1, count=0; fgets(buf, sizeof buf, fp); line++){
if(line >= nline){
fputs(buf, stdout); count++;
}
if(ncount && count == ncount)
break;
}
A. Bogatyrev, 1992-95 - 155 - Si v UNIX
fclose(fp); /* eto ne obyazatel'no pisat' yavno */
}
/* End_Of_File */
4.23. Sostav'te programmu, kotoraya raspechatyvaet poslednie n strok fajla vvoda.
4.24. Napishite programmu, kotoraya delit vhodnoj fajl na fajly po n strok v kazhdom.
4.25. Napishite programmu, kotoraya chitaet 2 fajla i pechataet ih vperemezhku: odna
stroka iz pervogo fajla, drugaya - iz vtorogo. Pridumajte, kak postupit', esli fajly
soderzhat raznoe chislo strok.
4.26. Napishite programmu sravneniya dvuh fajlov, kotoraya budet pechatat' pervuyu iz
razlichayushchihsya strok i poziciyu simvola, v kotorom oni razlichayutsya.
4.27. Napishite programmu dlya interaktivnoj raboty s fajlom. Snachala u vas zaprashi-
vaetsya imya fajla, a zatem vam vydaetsya menyu:
1. Zapisat' tekst v fajl.
2. Dopisat' tekst k koncu fajla.
3. Prosmotret' fajl.
4. Udalit' fajl.
5. Zakonchit' rabotu.
Tekst vvoditsya v fajl postrochno s klaviatury. Konec vvoda - EOF (t.e. CTRL/D), libo
odinochnyj simvol '.' v nachale stroki. Vydavajte chislo vvedennyh strok.
Prosmotr fajla dolzhen vestis' postranichno: posle vydachi ocherednoj porcii strok
vydavajte podskazku
--more-- _
(kursor ostaetsya v toj zhe stroke i oboznachen podcherkom) i ozhidajte nazhatiya klavishi.
Otvet 'q' zavershaet prosmotr. Esli fajl, kotoryj vy hotite prosmotret', ne sushchest-
vuet - vydavajte soobshchenie ob oshibke.
Posle vypolneniya dejstviya programma vnov' zaprashivaet imya fajla. Esli vy otve-
tite vvodom pustoj stroki (srazu nazhmete <ENTER>, to dolzhno ispol'zovat'sya imya fajla,
vvedennoe na predydushchem shage. Imya fajla, predlagaemoe po umolchaniyu, prinyato pisat' v
zaprose v [] skobkah.
Vvedite imya fajla [oldfile.txt]: _
Kogda vy nauchites' rabotat' s ekranom displeya (sm. glavu "|krannye biblioteki"),
perepishite menyu i vydachu soobshchenij s ispol'zovaniem pozicionirovaniya kursora v zadan-
noe mesto ekrana i s vydeleniem teksta inversiej. Dlya vybora imeni fajla predlozhite
menyu: otsortirovannyj spisok imen vseh fajlov tekushchego kataloga (po povodu polucheniya
spiska fajlov sm. glavu pro vzaimodejstvie s UNIX). Prosto dlya raspechatki tekushchego
kataloga na ekrane mozhno takzhe ispol'zovat' vyzov
system("ls -x");
a dlya schityvaniya kataloga v programmu|-
FILE *fp = popen("ls *.c", "r");
... fgets(...,fp); ... // v cikle, poka ne EOF
pclose(fp);
(v etom primere chitayutsya tol'ko imena .c fajlov).
4.28. Napishite programmu udaleniya n-oj stroki iz fajla; vstavki stroki posle m-oj.
K sozhaleniyu, eto vozmozhno tol'ko putem perepisyvaniya vsego fajla v drugoe mesto (bez
nenuzhnoj stroki) i posleduyushchego ego pereimenovaniya.
A. Bogatyrev, 1992-95 - 156 - Si v UNIX
4.29. Sostav'te programmu perekodirovki teksta, nabitogo v kodirovke KOI-8, v al'-
ternativnuyu kodirovku i naoborot. Dlya etogo sleduet sostavit' tablicu perekodirovki
iz 256 simvolov: c_new=TABLE[c_old]; Dlya resheniya obratnoj zadachi ispol'zujte stan-
dartnuyu funkciyu strchr(). Programma chitaet odin fajl i sozdaet novyj.
4.30. Napishite programmu, delyashchuyu bol'shoj fajl na kuski zadannogo razmera (ne v
strokah, a v kilobajtah). |ta programma mozhet primenyat'sya dlya zapisi slishkom bol'-
shogo fajla na diskety (fajl rezhetsya na chasti i zapisyvaetsya na neskol'ko disket).
#include <fcntl.h>
#include <stdio.h>
#define min(a,b) (((a) < (b)) ? (a) : (b))
#define KB 1024 /* kilobajt */
#define PORTION (20L* KB) /* < 32768 */
long ONEFILESIZE = (300L* KB);
extern char *strrchr(char *, char);
extern long atol (char *);
extern errno; /* sistemnyj kod oshibki */
char buf[PORTION]; /* bufer dlya kopirovaniya */
void main (int ac, char *av[]) {
char name[128], *s, *prog = av[0];
int cnt=0, done=0, fdin, fdout;
/* M_UNIX avtomaticheski opredelyaetsya
* kompilyatorom v UNIX */
#ifndef M_UNIX /* t.e. MS DOS */
extern int _fmode; _fmode = O_BINARY;
/* Zadaet rezhim otkrytiya i sozdaniya VSEH fajlov */
#endif
if(av[1] && *av[1] == '-'){ /* razmer odnogo kuska */
ONEFILESIZE = atol(av[1]+1) * KB; av++; ac--;
}
if (ac < 2){
fprintf(stderr, "Usage: %s [-size] file\n", prog);
exit(1);
}
if ((fdin = open (av[1], O_RDONLY)) < 0) {
fprintf (stderr, "Cannot read %s\n", av[1]); exit (2);
}
if ((s = strrchr (av[1], '.'))!= NULL) *s = '\0';
do { unsigned long sent;
sprintf (name, "%s.%d", av[1], ++cnt);
if ((fdout = creat (name, 0644)) < 0) {
fprintf (stderr, "Cannot create %s\n", name); exit (3);
}
sent = 0L; /* skol'ko bajt pereslano */
for(;;){ unsigned isRead, /* prochitano read-om */
need = min(ONEFILESIZE - sent, PORTION);
if( need == 0 ) break;
sent += (isRead = read (fdin, buf, need));
errno = 0;
if (write (fdout, buf, isRead) != isRead &&
errno){ perror("write"); exit(4);
} else if (isRead < need){ done++; break; }
}
if(close (fdout) < 0){
perror("Malo mesta na diske"); exit(5);
}
printf("%s\t%lu bajt\n", name, sent);
} while( !done ); exit(0);
}
A. Bogatyrev, 1992-95 - 157 - Si v UNIX
4.31. Napishite obratnuyu programmu, kotoraya skleivaet neskol'ko fajlov v odin. |to
analog komandy cat s edinstvennym otlichiem: rezul'tat vydaetsya ne v standartnyj
vyvod, a v fajl, ukazannyj v stroke argumentov poslednim. Dlya vydachi v standartnyj
vyvod sleduet ukazat' imya "-".
#include <fcntl.h>
#include <stdio.h>
void main (int ac, char **av){
int i, err = 0; FILE *fpin, *fpout;
if (ac < 3) {
fprintf(stderr,"Usage: %s from... to\n", av[0]);
exit(1);
}
fpout = strcmp(av[ac-1], "-") ? /* otlichno ot "-" */
fopen (av[ac-1], "wb") : stdout;
for (i = 1; i < ac-1; i++) {
register int c;
fprintf (stderr, "%s\n", av[i]);
if ((fpin = fopen (av[i], "rb")) == NULL) {
fprintf (stderr, "Cannot read %s\n", av[i]);
err++; continue;
}
while ((c = getc (fpin)) != EOF)
putc (c, fpout);
fclose (fpin);
}
fclose (fpout); exit (err);
}
Obe eti programmy mogut bez izmenenij translirovat'sya i v MS DOS i v UNIX. UNIX
prosto ignoriruet bukvu b v otkrytii fajla "rb", "wb". Pri rabote s read my mogli by
otkryvat' fajl kak
#ifdef M_UNIX
# define O_BINARY 0
#endif
int fdin = open( av[1], O_RDONLY | O_BINARY);
4.32. Kakim obrazom standartnyj vvod pereklyuchit' na vvod iz zadannogo fajla, a stan-
dartnyj vyvod - v fajl? Kak proverit', sushchestvuet li fajl; pust li on? Kak nado
otkryvat' fajl dlya dopisyvaniya informacii v konec sushchestvuyushchego fajla? Kak nado otk-
ryvat' fajl, chtoby poperemenno zapisyvat' i chitat' tot zhe fajl? Ukazanie: sm. fopen,
freopen, dup2, stat. Otvet pro perenapravleniya vvoda:
sposob 1 (bibliotechnye funkcii)
#include <stdio.h>
...
freopen( "imya_fajla", "r", stdin );
sposob 2 (sistemnye vyzovy)
#include <fcntl.h>
int fd;
...
fd = open( "imya_fajla", O_RDONLY );
dup2 ( fd, 0 ); /* 0 - standartnyj vvod */
close( fd ); /* fd bol'she ne nuzhen - zakryt'
ego, chtob ne zanimal mesto v tablice */
A. Bogatyrev, 1992-95 - 158 - Si v UNIX
sposob 3 (sistemnye vyzovy)
#include <fcntl.h>
int fd;
...
fd = open( "imya_fajla", O_RDONLY )