Už je tomu nějakou dobu, co jsme si vyzkoušeli velmi jednoduchý program typu filtr, který využíval standardní vstup a výstup. Tentokrát se na vstupně/výstupní operace podíváme podrobněji.

Ruby má jako základní abstrakci komunikace s okolním světem definovánu třídu IO, která představuje obousměrný komunikační kanál. Z třídy IO potom dědí třída File, která zajišťuje reprezentaci souborů a zajišťuje souborově orientovaný vstup a výstup. Dále z ní dědí třídy zajišťující síťovou komunikaci. Jako příklady instancí třídy IO nám poslouží již zmiňované standardní deskriptory, které jsou operačním systémem otevřeny každému spuštěnému procesu.

irb(main):001:0> STDIN.class
=> IO
irb(main):002:0> STDOUT.class
=> IO
irb(main):003:0> STDERR.class
=> IO
irb(main):004:0> $stdin.class
=> IO

Jedná se o standardní vstup (obvykle klávesnice terminálu) a standardní a chybový výstup (obvykle obrazovka terminálu). Jak je vidět jsou tyto deskriptory v Ruby dostupné jednak jako konstanty (velké písmeno na začátku) a pak jako globální proměnné (znak $) na začátku. Interpret při vstupně výstupních operacích používá hodnoty z globálních proměnných, které však můžeme za běhu programu změnit a tím standardní vstup nebo výstup přesměrovat. Původní hodnoty zůstavají zachovány v konstantách.

Třída IO definuje zejména obecné metody pro čtení a zápis, které často odpovídají funkcím ve standardní knihovně jazyka C. Pro čtení se jedná o metody:

  • getc – načtení jednoho bytu (při dosažení konce vstupu vrací nil),
  • gets – načtení jednoho řádku (volitelně lze zadat oddělovač, při dosažení konce vstupu vrací nil),
  • read – načtení maximálně daného počtu bytů (nebo celého souboru, není-li počet zadán),
  • sysread – jako read, ale s využitím nízkoúrovňových metod (např. nevyužívá vyrovnávací paměť),
  • readchar – jako getc, ale na konci souboru vyvolá výjimku,
  • readline – jako gets, ale na konci souboru vyvolá výjimku,
  • each_byte – čte vstup po bytech a pro každý spustí zadaný blok,
  • each_line – čte vstup po řádcích a pro každý spustí zadaný blok.

Analogicky máme k dispozici metody pro zápis: putc, puts, write a syswrite. Použít lze také metodu printf pro formátovaný výstup. Dále máme k dispozici několik metod, kterými můžeme zkoumat stav IO kanálu.

  • closed? – zjištění, zda je vstup nebo výstup uzavřený,
  • eof? – zjištění, zda bylo dosaženo konce vstupu,
  • tty? – zjištění, zda je daný deskriptor spojen s terminálem.

Ze základních metod nám ještě zbývá několik, kterými můžeme IO operace řídit:

  • close – uzavření vstupu nebo výstupu,
  • close_read – uzavření pro čtení,
  • close_write – uzavření pro zápis,
  • flush – vyprázdnění vyrovnávací paměti.

Ve třídě IO je ještě daleko více metod, které zde neuvádíme, protože jejich využívání obvykle vyžaduje hlubší znalost operačního systému. K některým se vrátíme zvlášť později. Nyní se pojďme podívat na nějaké další příklady. Jednoduché programy typu filtr jsme zatím psali pomocí while cyklu využívajícího skutečnosti, že metoda gets vrací na konci vstupu nil. Počítali jsme také s tím, že implicitním příjemcem metody je $stdin. Z výše uvedeného výčtu metod, je jasné, že bychom mohli postupovat mnoha alternativními způsoby. Jeden následuje a má navíc drobné vylepšení, které nám umožní vypsat hlášku v případě, že standardní vstup není přesmérován na soubor, jak bychom u filtru očekávali.

if $stdin.tty?
$stderr.puts „Tento program je filtr.“
else
$stdin.each_line { |l| $stdout.puts l.upcase }
end

Jak je vidět samotný kód filtru se nám vešel na jeden řádek. Otestujeme následovně (unixový terminál).

~$ ruby io1.rb
Tento program je filtr.
~$ echo „ahoj“ | ruby io1.rb
AHOJ

Třída File dědí všechny metody třídy IO a umožňuje nám je aplikovat na zvolený soubor. Instanci vytvoříme standardně voláním new, které otevře soubor na dané cestě. Pro vyzkoušení na unixovém systému můžeme použít následující krátký prográmek, který generuje heslo na základě bytů načítaných ze zařízení /dev/random. Toto zařízení na základě různých náhodných událostí (síťová komunikace, stisknutí kláves apod.) generuje náhodné byty, které lze číst podobně, jakoby byly uloženy v souboru. (V jiném operačním systému můžete pro testování použít libovolný dostatečně dlouhý soubor. Pouze vygenerované heslo bude pořád stejné.)

pwd = “
f = File.new(‚/dev/random‘)
while pwd.size < 8
c = f.readchar
pwd << c if ((c > ?a and c < ?z) or (c > ?A and c < ?Z))
end
f.close
puts pwd

V příkladu stojí za zmínku několik věcí. Jednak používáme metodu readchar. Protože nijak nezkoumáme, jestli náhodou otevřený soubor nehlásí konec souboru, máme takto zajištěno, že kdyby k tomu došlo, skončí program výjimkou. Dále si vybíráme jen takové načtené byty, které odpovídají malým nebo velkým písmenům (pomocí operátoru připojení, který umí zpracovat i kód znaku, je připojujeme k vytvářenému řetězci hesla). Ostatní byty zahazujeme. Teoreticky by se tedy mohlo stát, že se budou pořád generovat nevhodné znaky a program neskončí. V praxi se ale můžeme bezpečně vsadit, že ve velmi krátké době uvidíme výsledek.

~$ ruby io2.rb
IwDeKTeg

V prográmku je ještě jedno místo, kde může dojít k výjimce. Je jím samotné otevírání souboru při volání new. K výjimce by došlo například, pokud by soubor neexistoval nebo bychom na něj něměli příslušná přístupová práva. Ruby nabízí zajímavý způsob, jak provést operace se souborem jen v případě jeho úspěšného otevření. Metoda File.open se chov stejně jako new, ale navíc umožňuje předání bloku. Blok pak volá a jako parametr použije deskriptor otevřeného souboru. Návdavkem dojde po ukončení bloku k automatickém uzavření souboru. Náš příklad by pak vypadal následovně.

pwd = “
File.open(‚/dev/random‘) do |f|
while pwd.size < 8
c = f.readchar
pwd << c if ((c > ?a and c < ?z) or (c > ?A and c < ?Z))
end
end
puts pwd

V příštím díle budeme pokračovat dalšími souborovými operacemi. Do té doby, ale můžete vyzkoušet nabyté znalosti na následujících úlohách.

  • Vytvořte program, který spočítá kontrolní součet souboru. (Pro zjednodušení postačí prostý součet všech bytů.)
  • Vytvořte program, který porovná dva soubory, jestli jsou totožné.

1 Příspěvěk v diskuzi

Odpovědět