Portál AbcLinuxu, 2. května 2025 16:28
V závěrečném dílu seriálu si ukážeme, jakým způsobem se v Ruby pracuje se soubory, jak ošetřit výjimky, a jak své skripty propojit se systémem.
1. Práce se soubory
1.1 Nutné základy
1.2 Metoda gets
1.3 Metoda getc
1.4 Metoda read
1.5 Metoda readlines
1.6 Metoda each
(each_line
)
1.7 Metoda each_byte
1.8 Metoda lineno
1.9 Zápis do souboru
2. Ošetření výjimek
2.1 O co se jedná
2.2 begin...rescue...ensure...end
3. Kontakt s operačním systémem
3.1 Standardní vstup a výstup
3.2 Návratová hodnota skriptu
3.3 Spuštění s parametry
3.4 Název skriptu
3.5 Proměnné prostředí
3.6 Spouštění externích příkazů
4. Závěr
Píšeme-li program nebo skript, vyskytnou se situace, kdy budeme potřebovat pracovat s externími textovými soubory a klasické přesměrování vstupu a výstupu nám přestane stačit.
Příkaz File.open(soubor, mód)
vrací objekt
typu File
a jako parametry jsou mu předány název souboru a
mód. Mód udává to, co se souborem zamýšlíme dělat:
zápis: | význam: |
---|---|
r |
Otevře soubor pro čtení, ukazatel je na začátku souboru. Pokud soubor neexistuje, je vyvolána výjimka (viz dále). |
r+ |
Otevře soubor pro čtení i zápis, ukazatel je na začátku souboru. Pokud soubor neexistuje, je vyvolána výjimka. |
w |
Otevře soubor pro zápis, ukazatel je na začátku souboru. Pokud soubor existuje, je jeho obsah vymazán, pokud neexistuje, je vytvořen nový. |
w+ |
Otevře soubor pro čtení i zápis, ukazatel je na začátku souboru. Pokud soubor existuje, je jeho obsah vymazán, pokud neexistuje, je vytvořen nový. |
a |
Otevře soubor pro zápis na konec souboru, pokud soubor neexistuje, je vytvořen nový. Ukazatel je na konci souboru. |
a+ |
Otevře soubor pro čtení a zápis na konec souboru, pokud soubor neexistuje, je vytvořen nový. Ukazatel pro čtení je na začátku souboru, ukazatel pro zápis je vždy na jeho konci. |
pohadka.txt
pro čtení,
použili bychom následující zápis:
fin = File.open("pohadka.txt", "r")Jakmile je naše práce se souborem hotova, je vhodné jej zase zavřít a uvolnit tak místo v paměti počítače. To provedeme pomocí metody
close
:
fin.close
gets
Ke čtení ze souboru poskytuje jazyk Ruby hned několik užitečných metod,
z nichž jako první si ukážeme gets
. Ta slouží k načítání
jednotlivých řádků.
Pro názornost si v pracovním adresáři vytvoříme soubor
pokus.txt
s následujícím obsahem:
export PS1='\u@\h:\w\$ ' alias ls='ls --color' alias vlna='vlna -v KkSsVvZzOoUuAaIi'
V tomto adresáři spustíme nám již dobře známý interpretr
irb
a podíváme se, jak to vlastně funguje:
irb(main):001:0> bashrc = File.open("pokus.txt", "r") => #<File:pokus.txt> irb(main):002:0> bashrc.gets => "export PS1='\\u@\\h:\\w\\$ '\n" irb(main):003:0> bashrc.gets => "alias ls='ls --color'\n" irb(main):004:0> bashrc.gets => "alias vlna='vlna -v KkSsVvZzOoUuAaIi'\n" irb(main):005:0> bashrc.gets => nil irb(main):006:0> bashrc.close => nil
Z příkladu vidíme, že jakmile narazíme na konec souboru, vrátí
gets
prázdnou hodnotu nil
. Jelikož je tato
považována za false
, lze toho s výhodou využít. Jednoduchý
program pro vypsání obsahu souboru na obrazovku by pak mohl vypadat třeba
takto:
irb(main):007:0> bashrc = File.open("pokus.txt", "r") => #<File:pokus.txt> irb(main):008:0> while radek = bashrc.gets irb(main):009:1> print radek irb(main):010:1> end export PS1='\u@\h:\w\$ ' alias ls='ls --color' alias vlna='vlna -v KkSsVvZzOoUuAaIi' => nil irb(main):011:0> bashrc.close => nil
getc
Ne vždy se nám hodí čtení souboru po celých řádcích. Pro načítání
jednotlivých znaků můžeme použít metodu getc
. Ta vrací celé
číslo, které reprezentuje ASCII hodnotu načteného znaku. Chceme-li mít na
výstupu znaky samotné, musíme na ně nejprve převést celočíselné hodnoty
pomocí metody chr
:
irb(main):012:0> bashrc = File.open("pokus.txt", "r") => #<File:pokus.txt> irb(main):013:0> while znak = bashrc.getc irb(main):014:1> print znak.chr irb(main):015:1> end export PS1='\u@\h:\w\$ ' alias ls='ls --color' alias vlna='vlna -v KkSsVvZzOoUuAaIi' => nil irb(main):016:0> bashrc.close => nil
read
Na rozdíl od getc
vrací metoda read
řetězec
znaků typu String
, jejichž počet můžeme explicitně určit:
irb(main):017:0> bashrc = File.open("pokus.txt", "r") => #<File:pokus.txt> irb(main):018:0> bashrc.read(6) => "export"
Uvedeme-li více znaků než soubor ve skutečnosti obsahuje, načtou se znaky do konce souboru a zbytek je vynechán:
irb(main):019:0> bashrc.read(100) => " PS1='\\u@\\h:\\w\\$ '\nalias ls='ls --color'\nalias vlna='vlna -v KkS sVvZzOoUuAaIi'\n" irb(main):020:0> bashrc.close => nil
Konečně vynecháme-li parametr úplně, dojde k načtení celého souboru.
readlines
Metoda readlines
se od předchozích zásadně odlišuje.
Nevrací totiž jednotlivé znaky nebo řetězce znaků, ale celé pole řádků v
souboru:
irb(main):021:0> bashrc = File.open("pokus.txt", "r") => #<File:pokus.txt> irb(main):022:0> radky = bashrc.readlines => ["export PS1='\\u@\\h:\\w\\$ '\n", "alias ls='ls --color'\n", "alias vln a='vlna -v KkSsVvZzOoUuAaIi'\n"] irb(main):023:0> bashrc.close => nil
Další zajímavou vlastností je, že nám umožňuje zadat řetězec, který má
sloužit jako oddělovač pro načítání jednotlivých položek pole (implicitně
je jím konec řádku '\n
'):
irb(main):024:0> bashrc = File.open("pokus.txt", "r") => #<File:pokus.txt> irb(main):025:0> cucky = bashrc.readlines(" ") => ["export ", "PS1='\\u@\\h:\\w\\$ ", "'\nalias ", "ls='ls ", "--color'\na lias ", "vlna='vlna ", "-v ", "KkSsVvZzOoUuAaIi'\n"] irb(main):026:0> bashrc.close => nil
each
(each_line
)O metodě each
(popř. jejím synonymu each_line
)
jsme již jednou hovořili v 5.
díle našeho seriálu, kde jsme ji použili pro procházení řetězce po
jednotlivých řádcích. Nejinak je tomu se soubory. Stejného výsledku jako
cyklus z části 1.2 tedy můžeme dosáhnout následujícím způsobem:
irb(main):027:0> bashrc = File.open("pokus.txt", "r") => #<File:pokus.txt> irb(main):028:0> bashrc.each { |radek| print radek } export PS1='\u@\h:\w\$ ' alias ls='ls --color' alias vlna='vlna -v KkSsVvZzOoUuAaIi' => #<File:pokus.txt> irb(main):029:0> bashrc.close => nil
Podobně jako u tomu bylo u metody readlines
, i zde je možné
specifikovat oddělovač.
each_byte
Také metodu each_byte
jsme již probírali v souvislosti s
prací s řetězci. Omezím se proto jen na příklad, který je pro změnu
ekvivalentem ukázky z části 1.3:
irb(main):030:0> bashrc = File.open("pokus.txt", "r") => #<File:pokus.txt> irb(main):031:0> bashrc.each_byte { |znak| print znak.chr } export PS1='\u@\h:\w\$ ' alias ls='ls --color' alias vlna='vlna -v KkSsVvZzOoUuAaIi' => #<File:pokus.txt> irb(main):032:0> bashrc.close => nil
lineno
Metoda lineno
vrací vždy aktuální číslo řádku, který je
právě zpracováván. To se hodí v řadě případů, my si to ukážeme na
jednoduchém číslování řádků na výstupu:
irb(main):033:0> bashrc = File.open("pokus.txt", "r") => #<File:pokus.txt> irb(main):034:0> bashrc.each { irb(main):035:1* |radek| irb(main):036:1* printf "%4d %s", bashrc.lineno, radek irb(main):037:1> } 1 export PS1='\u@\h:\w\$ ' 2 alias ls='ls --color' 3 alias vlna='vlna -v KkSsVvZzOoUuAaIi' => #<File:pokus.txt> irb(main):038:0> bashrc.close => nil
S drobnými úpravami bychom si takto mohli generovat například HTML soubory s výpisy našich zdrojových kódů.
O nic složitější než čtení není ani zápis do souboru. K tomuto účelu
přitom můžeme využívat všech nám dosud známých metod, jakými jsou
puts
, print
, nebo printf
. Jeden
příklad za všechny:
irb(main):039:0> fout = File.open("pokus.txt", "w") => #<File:pokus.txt> irb(main):040:0> fout.puts "Zapsano prikazem puts." => nil irb(main):041:0> fout.print "Zapsano prikazem print.\n" => nil irb(main):042:0> fout.printf "PI=%4.2f\n", 3.141592 => nil irb(main):043:0> fout.close => nil
Soubor pokus.txt
by měl nyní obsahovat následující text:
Zapsano prikazem puts. Zapsano prikazem print. PI=3.14
Zvláště při práci s uživatelskými daty nastávají situace, kdy hrozí, že při nesprávné kombinaci zadaných hodnot se náš program v důsledku vzniklé chyby zhroutí. Představte si program, který od uživatele přijímá dvě číselné hodnoty a vypisuje jejich podíl. Dojde-li v takovém programu na dělení nulou, vyvolá interpretr výjimku, vypíše chybové hlášení a program se předčasně ukončí:
~$ ruby puts "Zde budeme delit nulou: " puts 3/0 puts "Tato cast programu se uz ale neprovede. :'(" ^D Zde budeme delit nulou: -:2:in `/': divided by 0 (ZeroDivisionError) from -:2
Příkazy za místem vzniku chyby tak nebudou nikdy provedeny. Podobný problém může nastat i při pokusu otevřít neexistující soubor pro čtení:
~$ ruby fin = File.open("levektekadruzab", "r") puts "Tento text nikdy nikdo nespatri." fin.close ^D -:1:in `initialize': No such file or directory - levektekadruzab (Errno::EN OENT) from -:1:in `open' from -:1
Toto chování je nejen nepříjemné pro uživatele, ale dělá také špatnou vizitku autorovi programu. Ruby proto nabízí nástroje k ošetření výjimek, které by měl každý slušný programátor využívat.
begin...rescue...ensure...end
Víme-li, že v určité části kódu hrozí předčasné ukončení v důsledku vzniku chyby, můžeme tento „nebezpečný“ blok označit a v případě vyvolání výjimky na něj patřičným způsobem zareagovat. K tomuto účelu nabízí Ruby následující konstrukci:
begin potenciálně nebezpečný kód rescue [SeznamVýjimek, …] kód vykonaný v případě vyvolání výjimky ensure kód vykonaný za každou cenu end
Vyskytne-li se v části za begin
výjimka, vykoná se kód v
bloku za klíčovým slovem rescue
. Můžeme se tak sami
rozhodnout, jak zareagovat – zda vypsat varovné hlášení, zapsat
událost do logu, ukončit program nebo se z chyby nějakým způsobem
zotavit.
irb(main):044:0> a = 42; b = 0 => 0 irb(main):045:0> begin irb(main):046:1* puts a / b # nebezpecna cast irb(main):047:1> rescue irb(main):048:1> puts "Chyba, pokus o deleni nulou!" irb(main):049:1> end Chyba, pokus o deleni nulou! => nil
Pokud za klíčovým slovem neuvedeme konkrétní typ výjimky, je použit
implicitní StandardError
. Chceme-li ale reagovat na různé
výjimky rozdílným způsobem, můžeme použít bloků rescue
i
více:
irb(main):050:0> begin irb(main):051:1* puts a / c # c neni definovano irb(main):052:1> rescue ZeroDivisionError irb(main):053:1> puts "Chyba, pokus o deleni nulou!" irb(main):054:1> rescue NameError irb(main):055:1> puts "Chyba, pouziti nedefinovane promenne." irb(main):056:1> end Chyba, pouziti nedefinovane promenne. => nil
Je zřejmé, že chybová hlášení v ukázkách jsou značně nepřesná a mnohdy
je výhodnější nechat jejich formulaci na interpretru. Naštěstí máme k
dispozici proměnnou $!
, která uchovává informace o poslední
vyvolané výjimce. Příklad s dělením nulou bychom tak mohli přepsat
takto:
irb(main):057:0> begin irb(main):058:1* puts a / b irb(main):059:1> rescue irb(main):060:1> puts "ERROR: " + $! irb(main):061:1> end ERROR: divided by 0 => nil
Za klíčovým slovem ensure
uvádíme úsek kódu, který je třeba
vykonat vždy, ať už k výjimce dojde nebo ne. Typickým příkladem je
uzavření souboru:
irb(main):062:0> begin irb(main):063:1* logfile = File.open("pokus.log", "a") irb(main):064:1> puts a / b irb(main):065:1> rescue irb(main):066:1> logfile.puts "ERROR: #{$!}" irb(main):067:1> puts "V aplikaci nastala chyba a byla ukoncena." irb(main):068:1> exit 1 irb(main):069:1> ensure irb(main):070:1* logfile.close irb(main):071:1> end V aplikaci nastala chyba a byla ukoncena. ~$
Při vypisování informací na obrazovku uživatele je vhodné rozlišovat mezi běžnými informacemi a chybovými hlášeními. Ruby proto poskytuje následující konstanty:
konstanta: | význam: |
---|---|
STDIN |
Standardní vstup. |
STDOUT |
Standardní výstup. |
STDERR |
Standardní chybový výstup. |
Ve výchozím nastavení jsou příkazy pro čtení napojeny právě na
STDIN
a příkazy pro zápis na STDOUT
, není proto
třeba tyto explicitně specifikovat. Nutné je však uvést, chceme-li text
přesměrovat na standardní chybový výstup:
~$ ruby > pokus.txt 2> pokus.err puts "Vypis na standardni vystup." STDERR.puts "Vypis na standardni chybovy vystup." ^D ~$ cat pokus.err Vypis na standardni chybovy vystup. ~$ cat pokus.txt Vypis na standardni vystup.
Stejně jako mají svou návratovou hodnotu metody, tak i jednotlivé programy informují systém o stavu, v jakém skončily. Konvence říká, že při úspěšném splnění úkolu by měly vrátit hodnotu 0, při ukočení v chybovém stavu pak malé kladné nenulové číslo.
Pro vynucení ukončení programu na libovolném místě slouží příkaz
exit
, který jsem už nenápadně propašoval do příkladu v sekci
2.2. Jako volitelný parametr přijímá celočíselnou hodnotu, která bude
vrácena systému. Je-li parametr vynechán, program se ukončí s hodnotou 0.
Drtivá většina programů a skriptů se v systémech unixového typu (Linux, *BSD, Solaris aj.) drží zásady, že je lze spouštět i bez nutnosti další interakce s uživatelem, a to předáním potřebných hodnot už při jejich spuštění. Díky tomu je možné práci s nimi automatizovat prostřednictvím skriptů nebo k nim napsat grafickou nástavbu; je proto vhodné tento přístup zachovat.
Při spuštění skriptu jsou všechny zadané parametry uloženy ve speciální
proměnné ARGV
(synonymně $*
) typu
Array
a práce s ní pro nás tedy nebude ničím novým. V
pracovním adresáři vytvořte soubor pokus.rb
s následujícím
obsahem:
ARGV.each_index { |index| printf "%2d %s\n", index, ARGV[index] }
Tento skript vypíše každý zadaný parametr na samostatný číslovaný řádek. Připomínám, že jednotlivé položky jsou v poli číslovány od nuly. Na výstupu tedy dostaneme něco takového:
~$ ruby pokus.rb prvni druhy 'a tady treti' 0 prvni 1 druhy 2 a tady treti
Je-li dávkově spouštěno více aplikací, nemusí být z chybových hlášení na první pohled zřejmé, která aplikace je vypsala. Je proto slušné ve výpisu uvést její název.
Název skriptu uchovává proměnná $0
typu
String
. Použití si ukážeme na příkladu, který přijímá přesně
jeden parametr a při jakémkoli jiném počtu vypíše informace o svém
použití:
if ARGV.length != 1 STDERR.puts "#{$0}: Chybny pocet parametru." STDERR.puts "Pouziti: #{$0} RETEZEC" exit 1 end
Tento kód opět uložíme do souboru pokus.rb
. Spuštění bez
parametrů pak proběhne následovně:
~$ ruby pokus.rb pokus.rb: Chybny pocet parametru. Pouziti: pokus.rb RETEZEC
Proměnné prostředí systému uchovává proměnná ENV
typu
Hash
. Login uživatele tak můžeme vypsat např. takto:
~$ ruby puts ENV['USER'] ^D blackened
Ke spouštění externích příkazů poskytuje Ruby hned několik ve svém chování mírně se lišících nástrojů, z nichž některé si nyní ukážeme.
Prvním způsobem je uzavření příkazu mezi zpětné apostrofy. Zadaný příkaz
je spuštěn v kopii shellu a jeho standardní výstup je navrácen jako typ
String
. Výše uvedený příklad se jménem uživatele lze proto
napsat také tímto způsobem:
~$ ruby puts `whoami` ^D blackened
Příkaz system
spustí zadaný příkaz v kopii shellu. V
případě, že tento skončí s nulovou návratovou hodnotou, je vráceno
true
, v ostatních případech false
:
~$ ruby if system("ruby pokus.rb 'Hello, World!'") puts "Program probehl v poradku." else puts "Za behu programu nastala chyba." end ^D Program probehl v poradku.
Konečně příkaz exec
nahradí současný proces zadanou
aplikací:
~$ ruby exec("date") puts "Tato cast nebude nikdy vykonana." ^D St dub 11 02:06:51 CEST 2007
Tento seriál si od začátku kladl dva cíle – být stručný a srozumitelný. Posouzení, nakolik se mi to podařilo, nechám na čtenáři. Text jsem se nicméně snažil bohatě prokládat názornými ukázkami, protože právě a jen praxí se člověk učí nejlépe.
Díky této koncepci se pochopitelně na spoustu věcí nedostalo. Pokud jste se však dočetli až sem, měli byste již být schopni pracovat nejen s jazykem samotným, ale také si další informace dohledat v jiných zdrojích.
File.open("fajl.txt") do |file| file.each do |line| xxx end endTim se lisi metoda
open
od new,
ktera jinak pracuje stejne, ale nelze ji predat proc
objekt. Jinak pointa toho celeho je mj. taky v tom, ze netreba zavirat soubor, zavre se automaticky pri dokonceni bloku.
class File ; def to_s ; read ; end ; end
... aneb je krasne mit neuzavrene definice trid ;)
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.