Komunikace se světem
Snad žádný proram se neobejde bez komunikace se svým okolím. V Ruby není těžké pracovat jak se standardním vstupem a výstupem, tak se soubory nebo síťovými sockety.
Standardní výstup
Uvedené ukázky zatím používaly pouze funkci puts, která vypíše parametr na standardní výstup a přidá znak konce řádku. Podobnou funkcí je print, který však znak konce řádku nepřidává. K dispozici je klasický printf, jehož formátovací specifikace je podobná té v jazyce C.
puts 'a' # vypiš 'a' a odřádkuj print 'b ' # vypiš 'b' a neodřádkuj puts 'c' printf "Číslo: %5.2f, řetězec: %s\n", 3.14, 'PI' # \n bude konec řádku
Po spuštění uvidíme:
a b c Číslo: 3.14, řetězec: PI
Zjednodušeně řečeno se při volání těchto funkcí ve skutečnosti volá metoda stejného jména náležející objektu třídy IO. Konkrétně se v našem případě jednalo o objekt reprezentující standardní výstup programu, který je přístupný přes globální proměnnou $stdout.
$stdout.puts 'Tak tak' # výpis na stdout
(Poznámka pro pozorné čtenáře: znak ‚$‘ není na začátku názvu proměnné náhodou. V Ruby existuje konvence pro pojmenování proměnných, která říká, že jména globálních proměnných začínají právě znakem ‚$‘. Ostatní proměnné, které používáme v příkladech, jsou lokální a jejich jména začínají malým písmenem.)
Standardní vstup
Ze standardního vstupu můžeme načíst řádek pomocí funkce gets. Opět se volá metoda objektu třídy IO – tentokrát $stdin. Pro čtení máme k dispozici i další metody. Podívejme se na příklady. Následující cyklus načítá řádky ze standardního vstupu a kopíruje je na standardní výstup:
while line=gets # buď načte řádek a pokračuje, nebo vrací nil a cyklus končí print line # vypiš řádek na stdout (gets ponechala znak konce řádku) end
Pokud bychom chtěli nejdříve načíst všechny řádky a pak je zpracovávat, mohli bychom to udělat například takto:
a=$stdin.readlines # načti řádky do pole řetězců puts a.size # počet prvků pole - tj. počet načtených řádků
Soubory
Viděli jsme, že vstup a výstup jsou realizovány pomocí metod třídy IO. Kromě standardního vstupu a výstupu můžeme využít také soubor nebo třeba síťový socket. Jak přečíst soubor?
f=File.new("soubor", "r") # metoda new vytvoří objekt třídy File while line=f.gets # úplně stejně jako u standardního vstupu print line end f.close # zavřeme soubor
Pro účely přístupu k souboru se využívá třídy File, která je velmi podobná třídě IO – můžeme volat všechny metody pro vstup a výstup, které jsme dosud použili. Metoda new, která vytváří nový objekt a v našem případě otevírá soubor, má dva parametry. První určuje název souboru a druhý mód otevření souboru (např. ‚r‘ znamená read – čtení, ‚w‘ znamená write – zápis).
Ukažme si ještě obecnost některých principů v Ruby:
f=File.new("soubor", "r") # metoda new vytvoří objekt třídy File f.each_line { |line| # iterátor volá blok a předává mu řádky print line } f.close # zavřeme soubor
Se souborem můžeme pohodlně pracovat pomocí iterátorů. V našem příkladu jsme zpracovávali soubor opět po řádcích, ale máme k dispozici i iterátor načítající soubor po bytech.
Argumenty a proměnné prostředí
Vstupními parametry programu mohou být samozřejmě také argumenty zadané z příkazové řádky a proměnné prostředí, ve kterém je program spuštěn. Argumenty jsou uloženy v globální proměnné ARGV, ke které přistupujeme stejnými metodami, jaké má třída Array. Oproti jazyku C není ARGV[0] cestou ke spuštěnému programu, ale skutečně prvním zadaným argumentem. Cesta ke spuštěnému programu se nalézá v globální proměnné $0.
Proměnné prostředí jsou uloženy v proměnné ENV, ke které lze přistupovat stejnými metodami, jako má třída Hash. Nejlépe je vše vidět na příkladu:
puts "Program: #{$0}" puts "Argumenty z příkazové řádky: #{ARGV.join(', ')}" puts "Proměnné prostředí:" ENV.each { |key,value| puts "#{key} = #{value}" }
Pokud uvedený kód bude uložen v souboru program.rb a spustíme jej pomocí příkazu ruby program.rb par1 par2 par3, obdržíme výpis podobný tomuto (výpis proměnných je krácen):
Program: test.rb Argumenty z příkazové řádky: par1, par2, par3 Proměnné prostředí: PWD = /home/dali/aoi/prg/ruby/tmp PAGER = less USER = dali COLORTERM = Eterm SHELL = /usr/local/bin/bash HOSTTYPE = i386 OSTYPE = freebsd4.4
Ošetření výjimek
Jako moderní programovací jazyk nabízí Ruby solidní podporu ošetření výjimek. K pochopení jejího fungování nejlépe poslouží demonstrativní prográmek.
#!/usr/local/bin/ruby begin # začátek bloku s výjimkami while line=gets # čti každý řádek až do konce souboru raise "Aaaa!" if line=~"^[aA]" # způsob výjimku, když řádek začíná na 'a' puts line # vypiš řádek end # konec cyklu (zde končí běžný program) rescue RuntimeError # odchyť výjimku třídy 'RuntimeError' puts "Chyba A!" # zobraz hlášku rescue Interrupt # odchyť výjimku třídy 'Interrupt' puts "\nKonec = CTRL-D." # zobraz hlášku retry # zpět na začátek bloku else # jinak (tj. pokud nedošlo k výjimce) puts "\nNedošlo k výjimce." # zobraz hlášku ensure # tento kód se vykoná tak jako tak - puts "\nKonec!" # ať došlo k výjice, či nikoliv end # konec bloku s výjimkami
Tento program může skončit buď korektně na konci souboru, který na standardním vstupu vytvoříme stiskem CTRL-D, nebo vznikem výjimky. Jednu výjimku způsobujeme v kódu přímo příkazem raise (jedná se o výjimku třídy RuntimeError. Druhá výjimka třídy Interrupt vznikne stiskem CTRL-C (tedy požadavkem na přerušení provádění programu).
Zatímco RuntimeError pouze zachytíme, zobrazíme hlášku a program se ukončí, po zachycení Interrupt se pomocí příkazu retry vrátí řízení na začátek bloku označený klíčovým slovem begin. Za příkazem else následuje kód, který se vykoná, pokud k žádné výjimce nedošlo. Za příkazem ensure následuje kód, který se vykoná v každém případě, ať již v bloku k výjimce došlo, či ne. Příkazy retry, else a ensure samozřejmě nejsou k fungování mechanismu výjimek nutné. Eventuální nezachycená výjimka ukončí činnost programu.
Podle konkrétních podmínek může spuštění proběhnout různě. Při korektním ukončení takto (je vidět i vstup od uživatele):
^D Nedošlo k výjimce. Konec!
Na tomto příkladu vidíme zotavení po stisku CTRL-C a pak ukončení na výjimce vyvolané řádkem začínajícím na ‚a‘:
ppp ppp ^C Konec = CTRL-D. aaa Chyba A! Konec!
V obou případech dojde k vypsání zprávy ‚Konec!‘.
Ošetření chyb a nestandardních situací v programech je zřejmě nejpracnější částí programování. Ačkoliv se jimi již nebudeme zabývat, nabízí Ruby ještě další cesty pro jejich zvládnutí.
Standardní knihovna
Veškeré příklady v tomto článku jsou založeny pouze na vestavěných objektových třídách a metodách jazyka Ruby. Pokud se dostaneme do situace, kdy si s těmito prostředky nevystačíme, čeká nás ještě velmi bohatá standardní knihovna. Jen namátkově naznačme, co lze od standardní knihovny Ruby očekávat:
- další datové struktury a operace nad nimi: např. komplexní čísla a datumy (konverze, výpočty atd.)
- podporu parsování složitých kombinací argumentů z příkazové řádky
- podporu profilování programu na úrovni interpreteru
- podporu serializace objektů a trvalého ukládání stavu objektů na disku
- podporu internetových síťových protokolů od síťové vrstvy (IP) až po aplikační vrstvu (HTTP, FTP, IMAP atd.)
- implementaci hashovacích funkcí
- podporu knihovny Curses
Za všechny uvedené oblasti si ukažme alespoň jeden příklad. Jedná se o jednoduchý TCP server, který přijme (libovolný) jednořádkový požadavek na portu 8081 a vrací krátký HTML dokument s aktuálním časem. Po spuštění si lze činnost serveru ověřit nasměrováním prohlížeče na adresu http://localhost:8081.
#!/usr/local/bin/ruby # Jednoduchý HTTP server naslouchající na portu 8081 vrací aktuální čas. # načteme potřebný modul standardní knihovny require 'socket' # hlavička dokumentu zaslaná klientovi head="HTTP/1.1 200/OK\r\nContent-type: text/html\r\n\r\n" # text zaslaný klientovi - znaky xxx později nahradíme aktuálním časem text=<<"EOS" <html> <body> <h1>Právě je xxx</h1> </body> </html>\r\n EOS # vytvoříme instanci TCP serveru server=TCPServer.new('localhost',8081) begin # budeme zachytávat výjimku puts "Start serveru..." while (session=server.accept) # čekáme na spojení puts "Požadavek: #{session.gets}" # přečteme data a vypíšeme je na stdout session.print head # vypíšeme hlavičku send=text.sub(/xxx/,Time.now.to_s) # nahradíme xxx za aktuální čas session.print send # vypíšeme připravený text session.close # uzavřeme spojení end rescue Interrupt # stisk CTRL-C korektně ukončí server puts "\nStop serveru..." end
Výpisy skriptu mohou vypadat třeba takto:
Start serveru... Požadavek: GET / HTTP/1.0 Požadavek: GET / HTTP/1.0 ^C Stop serveru...
Kromě standardní knihovny se stále rozšiřuje množství knihoven vytvořených programátory pro všechny představitelné úlohy. Jejich patrně nejúplnější seznam je k dispozici na stránce The Ruby Application Archive.
Příklad programu
Abychom se nezabývali stále jen příklady přitaženými za vlasy, podívejte se na výpis krátkého, ale relativně smysluplného programu, který faktorizuje čísla zadaná jako parametry z příkazové řádky nebo načítaná ze standardního vstupu.
#!/usr/local/bin/ruby # # factor.rb - triviální faktorizace čísla, příklad ke článku Ruby z rychlíku # # Funkce factor vrací první nalezený dělitel čísla n, případně číslo n # samotné, pokud je to prvočíslo. def factor n f=2 # dělitele začneme hledat od 2 while f<=Math.sqrt(n) # dělitel nebude větší než odmocnina čísla return f if n.remainder(f)==0 # pokud je zbytek 0, je f dělitel a končíme f+=1 # jinak inkrementujeme f a opakujeme cyklus end return n # nebyl nalezen dělitel, vracíme n end # Funkce factorize hledá dělitele čísla n a ukládá je do pole, které je # návratovou hodnotou. Pokud je číslo n prvočíslem, vrátí se v poli jen # jeden prvek - samotné n. def factorize n x,a=n,[] # do x uložíme n a vytvoříme prázdné pole a while x!=(y=factor(x)) # dokud x není prvočíslo (x==factor x, viz fce factor) a.push(y) # y je dělitelem x, uložíme y do pole faktorů x/=y # x vydělíme y a pokračujeme faktorizací výsledku end a.push(y) # uložíme poslední výsledek fce factor return a # vrací se pole faktorů end # Funkce printFactors vypíše na standardní výstup číslo n a jeho faktory. def printFactors(n,a) print n.to_s.ljust(10)+': ' # n převedeme na string a doplníme mezerami if a.size>1 # pokud jsou v poli faktory, puts a.join(', ') # vypíšeme je oddělené čárkami, else # jinak puts 'je prvočíslo' # je n prvočíslo end end help=<<EOS factor.rb - triviální faktorizace čísla, příklad ke článku Ruby z rychlíku použití: factor.rb [číslo1 číslo2 ...] (pokud nejsou zadány parametry, načítá ze standardního vstupu) EOS # Zde začíná hlavní část programu. if ARGV.size>0 # jsou-li parametry z příkazové řádky, if ARGV[0]=~/-h|--help/ # zkontroluj, zda není žádost o help puts help # výpis helpu exit # normální ukončení programu end ARGV.each { |n| # jinak projdi parametry po jednom, n=n.to_i.abs # převeď na integer a absolutní hodnotu printFactors(n,factorize(n)) if n>0 # a faktorizuj ty větší než nula } else # když nejsou argumenty begin # budeme zachytávat výjimky while n=gets # načítej řádky ze standardního vstupu, n=n.to_i.abs # převeď na integer a absolutní hodnotu raise if n==0 # pokud to bylo např. písmeno, výjimka, printFactors(n,factorize(n)) # jinak faktorizuj end rescue Interrupt # stisk CTRL-C puts "\n* Stiskněte CTRL-D!" # potřebujeme " kvůli \n retry # opakujeme od začátku rescue RuntimeError # 'ručně' vyvolaná výjimka puts '* Neplatný vstup!' retry # opakujeme od začátku end end
Po spuštění bez parametrů může dialog s uživatelem vypadat například takto:
23456789 23456789 : je prvočíslo 123456789 123456789 : 3, 3, 3607, 3803 a * Neplatný vstup! ^C * Stiskněte CTRL-D! as * Neplatný vstup!
Na co místo nezbylo
Ruby má za sebou přeci jen několik let vývoje a jedná se o značně bohatý jazyk. Jak z hlediska svých vlastností, tak i vytvořených knihoven a aplikací. Stručný průřez v tomto článku nemůže být ani zdaleka kompletní. Z nejzajímavějších témat, o nichž nebyla řeč, vyberme například:
- objektové programování – Ruby je jazyk od základu objektově orientovaný a zaslouží si samostatné povídání o práci s objekty
- zabudovaná bezpečnostní opatření – každý objekt má například příznak, zda jsou pro něj povoleny určité operace: pro řetězec načtený ze souboru lze třeba stanovit, že nepůjde předat operačnímu systému jako parametr ke spuštění
- thready – interpret Ruby má vlastní platformově nezávislou a přenositelnou implementaci threadů (v Ruby fungují thready dokonce i v DOSu)
- interakce s grafickým uživatelským prostředím – pro Ruby existují knihovny umožňující vytvářet aplikace pro různá GUI včetně rozhraní pro GTK, Qt a MS Windows
Zdroje
- Matsumoto, Y.: Ruby in a Nutshell, O'Reilly & Associates, 2001
- Thomas, D. – Hunt, A.: Programming Ruby