Portál AbcLinuxu, 1. listopadu 2025 12:22
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
getsKe č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
getcNe 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
readNa 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.
readlinesMetoda 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_byteTaké 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
linenoMetoda 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...endVí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
end
Tim 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.