Jak jsem slíbil, tak činím. Pro zatím se ale nebudeme pouštět do žádných větších akcí ;) a tak se podíváme jak pracovat se soubory. Jako i jindy se lze k jednomu výsledku dopracovat několika způsoby, některé si tedy představíme.
Ve vale, se k základní práci se soubory používá třída FileStream. Vytváří se statickou metodou open s parametry cesty k souboru a módem otevření, nebo fopen s parametry file descriptor a mód. Tedy celkem standarní přístup. A jak by asi C / C++ programátoři očekávali, třída obsahuje standardní metody jako printf, putc, getc, eof, flush, seek, read, write a další.
V minulém článku jsem zmínil základní virtuální soubory, běžně dostupné na všech posixových systémech stdin, stdout a stderr. Tyto soubory jsou dostupné jako proměnné balíku glib a proto je není nutné nijak inicializovat. A protože je GLib namespace implicitně dostupný, není třeba jej explicitně uvádět.
public static int main (string[] args){
char x = (char) stdin.getc();
stderr.printf("%s: Chybový text\n", args[0]); // Netřeba uvádět GLib namespace
GLib.stdout.printf("Zadali jste znak %c\n", x); // nic ale nezkazíme ;)
return 0;
}
Soubor stdinouterr.vala
Správnost si můžete ověřit například přesměrování jednoho z výstupů do /dev/null, nebo do souboru:
~$ valac stdinouterr.vala
~$ echo E | ./stdinouterr
~$ echo K | ./stdinouterr 2>/dev/null
Třída FileStream obsahuje běžné metody, které znáte z jiných jazyků, a které Vám umožní číst a zapisovat znaky, řádky nebo bloky dat. Pro čtení řádků se vysloveně hodí metoda read_line, a pro zápis printf s koncem řádku na konci.
public static int main (string[] args) {
// Otevře soubor filestream.vala pro čtení
FileStream stream = FileStream.open ("filestream.vala", "r");
assert (stream != null); // neúspěch ukončí aplikaci
string ? line; // obsah proměnné line může být roven null
// line je přirazena hodnota z read_line a ta je následně testována
// na null
while ((line = stream.read_line()) != null) {
stdout.puts(line); // do stdout vloží přečtený řádek
stdout.putc('\n'); // do stdout vloží znak nového řádku
}
return 0;
}
soubor filestream.vala
V uvedeném příkladu používám některé již probrané možnosti, ale raději je znovu vysvětlím. Nejprve ale od začátku. Otevřeme soubor filestream.vala pro čtení, název i s módem otvírání je předat v parametrech statické metodě FileStream.open.
Příkazem assert otestujeme správné vytvoření třídy, ale vždyť stream nemůže být null?! Ve skutečnosti může. Deklarace bez otazníku způsobí, že na některých místech volá Vala assert za nás, ne však všude. V našem příkladu tento assert test být nemusí, protože volání metody read_line interně tento test stejně provede. Díky tomu by ani nemusel být string line deklarován z otazníkem. Programátorovi i překladači by ale mělo být zřejmé, co v proměnné může být. Budeme chovat slušně a ono se nám to dříve, nebo později rozhodně vyplatí.
Konstrukce while by neměla být ničím zajímavá, prostě načte díky metodě read_line řádek, a ten pak testuje na správné přiřazení. Pokud vše proběhne jak má, na stdout se vloží přečtený řádek a po něm i znak nového řádku, protože read_line vrací znak bez něj.
Než se posuneme dál, zastavím se ale u metody read_line. Možná byste místo složitého přiřazování a následného porovnávání přečteného řádku, chtěli použít metodu eof. Je třeba si ale uvědomit, že metoda read_line ve skutečnosti čte znak po znaku (což můžete dělat také), a pokud narazí na znak '\n' nebo na EOF, ukončí čtení. A pokud do té doby nic nepřečetla (ani konec řádku), vrátí null, nikoli prázdný string. Díky tomu, že většina editorů na konci posledního řádku, vkládá znak odřádkování a pak až znak EOF, může se snadno stát, že EOF přečtený nebude, a read_line vrátí null, takže kontrola metodou eof nebude fungovat.
A co uzavření souboru ? FileStream je třída, a ta má svůj konstruktor i destruktor, ten sice není vidět, ale volá se v době, kdy Vala zjistí, že už objekt nepotřebujete. Na místě tohoto destruktoru uvidíte ve vygenerovaném C souboru právě uzavření otevřeného souboru.
Za domácí úkol si zkuste tento primitivní příklad rozšířit o délku přečteného řádku, nebo o jeho číslo.
Metody read a write
Dřív nebo později ovšem budete chtít číst, nebo zapisovat binární data. Logickým krokem, by pro Vás mohlo být použití metod read a write. Jejich použití je ale malinko zrádné a je třeba u nich přemýšlet v čistém C.
Metody očekávají pole čísel o velikosti 8mi bitů (uint8). Pokud máte data uložena v poli čísel, vlastně nemusíte druhý parametr zadávat, implicitní hodnota je 1. Pokud ale přetypováváte pole nějakých struktur, počet položek v poli se nemění, ale velikost struktury ano. Musíte pak uvést druhý parametr, ve kterém říkáte jak velká ta datová struktura je.
Problém může také nastat, pokud poslední čtení nepřepíše celý buffer. Prakticky se to dá obejít tak, že zpracovávat budete tu část bufferu, do které byla data skutečně načtena. Oba problémy si ukážeme v následujících příkladech:
public static int main (string[] args) {
// Otevře soubor filestream.vala pro čtení v binárním režimu
FileStream stream = FileStream.open ("filestream2.vala", "rb");
uint8 buf[20]; // pole 20ti byte
size_t size;
while ((size = stream.read(buf)) > 0) { // přečte jeden blok 20ti čísel
stdout.write(buf[0:size]); // do stdout vloží výsek z bloku
}
return 0;
}
soubor filestream2.vala
Otevření souboru, resp. vytvoření objektu třídy FileStream už známe, jen je doplněn mód o binární příznak. Tento příznak vlastně na posixových systémech nic nedělá, ale je dobré ho uvádět pro lepší orientaci. Následuje deklarace pole 20ti čísel o velikosti jeden byte. Toto pole by bylo v čistém C naprosto neinicializované, Vala ovšem vygeneruje kód, ve kterém inicializuje první hodnotu a tím donutí překladač nastavit všechny položky pole na hodnotu 0.
Konstrukce while je velmi podobná předchozímu příkladu. Zajímavé je však volání metody read. Metoda, jak už sem napsal, přečte pole čísel o velikosti definovaného pole, v našem případě tedy 20.
Následuje zápis pole do stdout. Výsek z pole (buf[0:size]) bude známý zejména pythonistům. Tento zápis vytvoří pole o nové velikosti, která je definovaná právě rozsahem v hranatých závorkách. Pokud by metoda write dostala celé pole, zapsala by i část nepřepsaných dat přečtených v předchozím cyklu, což není žádaný stav. Nakonec ještě zmíním, že v případě metody printf by toto samozřejmě nefungovalo, protože metoda printf očekává string, tedy pole znaků, které je ukončeno hodnotou 0 (což nemusí být pravda) a nově vytvořené pole buf[0:size], by skoro určitě ukazovalo na původní data v paměti.
public struct Data { // jednoduchá datová struktura
char l;
char h;
public Data(char x) { // i struktura může mít konstruktor
l = x.tolower();
h = x.toupper();
}
}
public static int main (string[] args) {
// pole datových struktor
Data [] mem = {Data('a'), Data('h'), Data('o'), Data('j')};
stdout.write((uint8 []) mem, sizeof(Data)); // zapis pole struktur
// sizeof vrací velikost struktury Data v paměti
stdout.putc('\n');
return 0;
}
soubor memstream.vala
V první části programu definujeme veřejnou strukturu Data. O strukturách jsem psal v úvodní části tohoto seriálu. Jde vlastně o velmi jednoduchou podobu objektu. Struktura má dvě vlastnosti l a h. V konstruktoru této struktury tyto vlastnosti nastavujeme.
a A
h H
o O
j J
Obraz paměti, jak v ní je uloženo pole mem
Magie začíná. V druhé části, tedy ve funkci main, vytváříme pole těchto struktur. Pole má 4 prvky a každý prvek zabírá v paměti 2 znaky (byty). Přetypováním tohoto pole na pole bytů získáme zase pole o čtyřech prvcích. První byte ukazuje na první znak první struktury, druhý na druhy znak první struktury, třetí na první znak druhé struktury a čtvrtý na druhý znak druhé struktury. Ale protože potřebujeme zapsat všechny prvky v poli mem, musíme metodě write předat jako druhý parametr velikost jedné datové struktury. Metoda write pak bude správně číst data z paměti od adresy proměnné mem po adresu mem + mem.length * sizeof(Data).
Pokud náhodou nevíte, co dělá sizeof, věřte, že vrací velikost typu v bytech.
Samozřejmě předpokládáme, že velikost jednoho byte je 8 bitů a proto používáme uint8.
V dokumentaci metody read a write je bohužel velmi zavádějící chyba. Metody se opravdu chovají tak, jak jsem popsal, což si lze snadno ověřit studiem vygenerovaného C souboru.
File
Než takový soubor otevřeme, je vhodné ho například otestovat, zda existuje. Někdy navíc potřebujeme se souborem dělat další „systémové“ věci. Kopírovat ho, vytvářet, měnit jeho parametry, získat jeho cestu atd. K tomu slouží třída File z balíčku gio. V minulém díle jsem naznačil cosi o dalších vala knihovnách – balíčcích. S těmi se budeme postupně setkávat, dnes použijeme první z nich. Něž k tomu však dojde, malinko odbočím.
Dalo by se říci, že balíček je synonymum pro knihovnu. Je třeba ale mít na paměti, že tomu tak vždy nemusí být. Autor dotyčného vapi (vala api) balíčku, může za jeden takový balíček schovat podporu několika souvisejících knihoven, nebo obráceně, jednu knihovnu rozdělit do několika balíčků. Standardní vala balíček odpovídá balíčku definovaného přes pkg-config. Pkg-config je nástroj, který vrátí potřebné informace pro kompilování a linkování s danou knihovnou. Tento nástroj využívá celá rodina GNOME knihoven, od té nejspodnější – GLib, až po tu nejvyšší - GNOME. A možná nejen díky tomu, že podporuje i závislosti, nebo kontrolu verzí, existuje podpora pkg-configu napříč celým open-source světem. Jedna knihovna, má často několik samostatných balíčků, a tak do budoucna budu vždy uvádět pouze balíček, a případně i celou knihovnu, ze které balíček je.
Nyní už si můžeme ukázat malý kód na testování přítomnosti souboru.
public static int main (string[] args) {
// vytvoření třídy jedním z konstruktorů:
File file = File.new_for_path ("ctime.txt");
if (file.query_exists ()) { // kontrola zda soubor existuje
stdout.printf ("Soubor '%s' existuje\n", file.get_path());
} else { // nebo nikoli
stdout.printf ("Soubor 'ctime.txt' neexistuje\n");
}
return 0;
}
soubor vala_exist.vala
Uvedený příklad kompilujeme, je však nutné uvést balíček gio, jež obsahuje třídu File:
~$ valac file_exist.vala --pkg=gio-2.0
Co se vlastně v programu děje? Nejprve vytvoříme objekt file, podobně jako v případě FileStream, jednou ze statických metod. Třída File má několik statických metod pro vytváření svých instancí, my vytváříme objekt z názvu souboru včetně cesty, konkrétně z relativní cesty. No a proč neuvádím namespace ? Protože balíček gio patří do rodiny GLib knihoven, a tak používá GLib namespace, který jak už jsme si řekli, je standardně integrován do všech vala programů.
Zbytek kódu testuje, zda soubor existuje, a pokud existuje, je vypsána celá jeho cesta. To znamená, že relativní cesta je doplněna o aktuální adresář, ve kterém se program pouští, a tedy ve kterém se soubor ctime.txt nachází.
Třída File obsahuje mnoho zajímavých metod, některé dokonce rovnou otvírají soubory pro čtení nebo zápis. Jsou to například metody append_to, crate, read nebo jejich asynchroní modifikace. Tyto metody vrací objekty, které reprezentují opravdové soubory. Nejde však o FileStream ale o InputStream, OutputStream nebo IOStream, který je zastřešuje, nikoli však objektově (oba objekty jsou vlastnostmi).
Tyto třídy jsou též součástí gio balíčku, dědí z třídy GLib.Object a tedy dědí i její vlastnosti jako je ref_count nebo signal notify. Všechny zmíněné třídy z gio balíčku již pracují s výjimkami.
public static int main (string[] args) {
// Vytvoří třídu file, nikoli soubor test.log
File file = File.new_for_path ("test.log");
try { // v bloku může nastat výjimka
// až zde se vytvoří soubor test.log, pokud ovšem neexistuje
FileOutputStream os = file.append_to (FileCreateFlags.NONE);
// objekt string, má vlastnost data, která je čirou náhodou uint8[]
os.write ("Další nový řádek\n".data);
// odchycení výjimky GLib.Error
} catch (Error e) { // odchycení výjimky
stdout.printf ("Error: %s\n", e.message);
return 1;
}
return 0;
}
soubor append_to_log.vala
Jak je v ukázce uvedeno, vytvoření objektu file rozhodně nevytváří žádný soubor. To je mimo jiné také důvod proč tato třída nemá žádnou metodu close. Zato jeho metoda append, již soubor vytváří, parametrem mu říkáme zda má soubor vytvořit přístupný všem, nebo jen uživateli, pod nímž je program spuštěn. O to jak se to děje na různých systémech se starat nemusíme, na každém to znamená něco jiného. Většina knihoven, které budeme používat a na kterých je Vala, resp, GTK, potažmo GNOME postaveno, obsahuje jistou míru abstrakce, díky níž nemusíte větvit program podle toho, na jakém systému byl spuštěn.
Třída FileOutputStream má mimo jiných metodu write, které se stejně jako v případě FileStream předává pole čísel – bytů. A čirou náhodou objekt string, i když je to objekt spíše abstraktní, obsahuje vlastnost data, která se nám hodí. A ani zde soubor nezavíráme, i když nám to třída FileOutputStream dovoluje. I zde platí že ztráta reference na objekt vyvolá destruktor, a ten se o vše postará sám. Ve vygenerovaném C souboru již žádné volání close nenajdete, to proto, že jde o potomky třídy GLib.Object a u nich se i v čistém C volá „destruktor“ funkcí g_object_unref. To mimo jiné znamená, že tuto funkcionalitu mají třídy z gio balíčku i ve své čisté C podobě.
K výjimkám se ještě dostaneme, proto jejich popis zatím vynechám a kód řádně otestujeme. Zkompilujeme, dvakrát spustíme, změníme práva výstupního souboru, a ověříme, že opravdu došlo k chybě.
Balíček gio toho však obsahuje daleko víc, než jen pár tříd pro práci se soubory. Umí pracovat i s disky, sockety, sítí, nebo třeba s uživatelským nastavením aplikace. Pokud budete potřebovat nějaký balíček, pomocí nějž byste otvírali soubory ze sítě, gio je ten, který hledáte. Vedle gio balíčku existuje ještě gio-unix, ten obsahuje třídy, jenž využijete jen na unixových systémech (unix socket nebo disková přípojná místa).
Od příštího dílu již začneme probírat grafické rozhraní GTK+. Je toho celkem dost, tak doufám že se máte na co těšit. Na úplný závěr ještě zmíním, že gio je součástí GLib knihovny (je distribuován pohromadě), tedy zatím používáme to, co je pro Valu nejzákladnější vybavení.
28.11.2018 23:56/František Kučera Prosincový sraz spolku OpenAlt se koná ve středu 5.12.2018 od 16:00 na adrese Zikova 1903/4, Praha 6. Tentokrát navštívíme organizaci CESNET. Na programu jsou dvě přednášky: Distribuované úložiště Ceph (Michal Strnad) a Plně šifrovaný disk na moderním systému (Ondřej Caletka). Následně se přesuneme do některé z nedalekých restaurací, kde budeme pokračovat v diskusi.
Komentářů: 1
12.11.2018 21:28/Redakce Linuxsoft.cz 22. listopadu 2018 se koná v Praze na Karlově náměstí již pátý ročník konference s tématem Datová centra pro business, která nabídne odpovědi na aktuální a často řešené otázky: Jaké jsou aktuální trendy v oblasti datových center a jak je optimálně využít pro vlastní prospěch? Jak si zajistit odpovídající služby datových center? Podle jakých kritérií vybírat dodavatele služeb? Jak volit vhodné součásti infrastruktury při budování či rozšiřování vlastního datového centra? Jak efektivně datové centrum spravovat? Jak co nejlépe eliminovat možná rizika? apod. Příznivci LinuxSoftu mohou při registraci uplatnit kód LIN350, který jim přinese zvýhodněné vstupné s 50% slevou.
Přidat komentář
6.11.2018 2:04/František Kučera Říjnový pražský sraz spolku OpenAlt se koná v listopadu – již tento čtvrtek – 8. 11. 2018 od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5). Tentokrát bez oficiální přednášky, ale zato s dobrým jídlem a pivem – volná diskuse na téma umění a technologie, IoT, CNC, svobodný software, hardware a další hračky.
Přidat komentář
9.9.2018 14:15/Redakce Linuxsoft.cz 20.9.2018 proběhne v pražském Kongresovém centru Vavruška konference Mobilní řešení pro business.
Návštěvníci si vyslechnou mimo jiné přednášky na témata: Nejdůležitější aktuální trendy v oblasti mobilních technologií, správa a zabezpečení mobilních zařízení ve firmách, jak mobilně přistupovat k informačnímu systému firmy, kdy se vyplatí používat odolná mobilní zařízení nebo jak zabezpečit mobilní komunikaci.
Přidat komentář
12.8.2018 16:58/František Kučera Srpnový pražský sraz spolku OpenAlt se koná ve čtvrtek – 16. 8. 2018 od 19:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát jsou tématem srazu databáze prezentaci svého projektu si pro nás připravil Standa Dzik. Dále bude prostor, abychom probrali nápady na využití IoT a sítě The Things Network, případně další témata.
Přidat komentář