V minulém díle jsme si ukázali, jak vytvořit jednoduchý program. Mohli bychom říct skript, protože Ruby nám nevnucuje žádnou povinnou strukturu či náležitosti. V zásadě stačí napsat několik volání metod za sebe a jednoduchý program je hotov. Toto je záměr návrhu, protože Matz chtěl vytvořit jazyk stejně silný a vhodný pro použití systémovými administrátory jako je jazyk Perl. Pojďme se podívat, na některé typické vlastnosti, které administrátor může v denní praxi potřebovat, a jak je v Ruby používat.

Jednou z možností je používat Ruby jako takzvaný „glue“ (doslova „lepidlo“) jazyk. To může znamenat třeba skript, který postupně spouští různé další programy a skripty a zajišťuje jejich vzájemnou komunikaci a součinnost. Jako jednoduchý příklad si představme, že máme stroj s nějakým unixovým operačním systémem a potřebujeme ukončit procesy určitého uživatele. Náš OS k tomu nemá vhodný příkaz, ale zato máme nainstalované Ruby. K dispozici ovšem máme standardní příkaz ps, který vypisuje seznam souborů, a příkaz kill, který danému procesu zasílá (bez dalších parametrů) signál TERM. K příkazu ps použijeme doplňkové parametry, abychom získali výpis všech procesů ve formátu se jménem uživatele. Parametrem příkazu kill bude identifikátor procesu – PID. Zkrácená ukázka následuje.

ara:~$ ps -jax
USER   PID   PPID   PGID  SID   JOBC  STAT  code  TIME     COMMAND
root   645   1      645   645   0     Is    ??    0:00.03  /sbin/devd
root   662   1      662   662   0     Ss    ??    0:19.25  /sbin/routed -q
root   717   1      717   717   0     Ss    ??    0:26.76  /usr/sbin/syslogd -l /v
pgsql  963   1      963   963   0     Ss    ??    12:49.11 /usr/local/bin/postgres
dali   25462 25460  25462 25462 0     Ss    p0    0:00.04  -bash (bash)
dali   25478 25462  25478 25462 1     R+    p0    0:00.01  ps -jax

Prvním úkolem bude dostat výpis z ps do Ruby skriptu, abychom s ním mohli dále pracovat. Jako správní administrátoři použijeme způsob, který je nejjednodušší a nejvíce nám šetří prsty. Vypadat by to mohlo takhle.

ps = `ps -jxa`
puts ps.class

Řetězec v obrácených apostrofech je interpretem použit jako příkaz, který je spuštěn v příslušném příkazovém procesoru (shellu) v rámci OS (funguje to i na MS Windows, kde ale není k dispozici příkaz ps). Textový výstup spuštěného procesu je interpretem zachycen a uložen jako objekt String (což se dozvíte, když uvedený fragment kódu spustíte). Referenci na objekt máme v proměnné ps. Nyní potřebujeme procházet řetězec po jednotlivých řádcích a na každém řádku nás zajímá první a druhý sloupeček. Skript upravíme například následovně.

ps = `ps -jxa`
ps.each_line do |line|
  fields = line.squeeze(‚ ‚).split(‚ ‚).first(2)
  puts fields.join(‚, ‚)
end

Využíváme zde povětšinou technik, které se objevily v minulých dílech. Nový je iterátor each_line třídy String, který (jak již název napovídá) předává do bloku postupně každý řádek. Ten nejdříve zpracujeme metodou squeeze, která pro každý zadaný znak nahradí v řetězci násobný výskyt pouze jedním znakem. V našem případě tak odstraníme nadbytečné mezery. Navazuje rozdělení řetězce na pole a vybrání prvních dvou prvků tohoto pole. Výsledek si pro kontrolu vypíšeme (ukázka je opět krácena).

ara:~$ ruby killuser.rb
USER, PID
root, 645
root, 662

Jméno uživatele, jehož procesy chceme ukončit, předáme skriptu jako parametr příkazové řádky. K dispozici ho budeme mít jako první prvek (s indexem 0) speciálního pole ARGV. Pro řádky, kde první pole odpovídá zadanému jménu, spustíme příkaz kill. Tentokrát k tomu použijeme metodu system, která opět předává parametry ke spuštění shellu, ale nezachycuje výstup.

ps = `ps -jxa`
ps.each_line do |line|
  fields = line.squeeze(‚ ‚).split(‚ ‚).first(2)
  if fields[0] == ARGV[0]
    puts „Budu volat kill #{fields[1]}“
    system(„kill“, fields[1].to_s)
  end
end

Pokud nejste přihlášeni jako uživatel root, můžete skript bez obav vyzkoušet se jménem nějakého jiného uživatele. Díky omezením v právu zasílat signály procesům skončí spuštěné příkazy chybou. Namísto spouštění externího příkazu kill bychom mohli použít knihovní metodu stejného jména: Process.kill("SIGTERM", pid), kde bychom v proměnné pid měli PID procesu. Malinko předběhneme, abychom si řekli, že Process je v tomto případě název modulu, v němž si můžete vyhledat v dokumentaci různé další metody související s procesy. Prozatím se ještě podívejme na to, že metoda system vrací pravdivostní hodnotu podle toho, zda se zadaný příkaz podaří úspěšně spustit a zda skončí úspěšně (s návratovým kódem 0).

irb(main):001:0> system(‚echo‘, ‚Hello‘)
Hello
=> true
irb(main):002:0> system(‚neexistuje‘, ‚Hello‘)
=> false

Přímo PID naposledy spuštěného externího procesu a jeho návratový kód najdeme v globální proměnné $?. Jako příklad této funkcionality si udělejme malý skript, který vypíše hlášku v případě nedostupnosti daného počítače. Dostupnost budeme testovat příkazem ping s parametrem, který stanoví, že se bude testovat pouze jedním ICMP paketem.

available = system(„ping -c1 #{ARGV[0]} > /dev/null 2>&1“)
if available
  puts ‚Dostupný‘
else
  puts ‚Nedostupný‘
end

V tomto případě je parametr metodě system předán v jednom kusu s interpolací názvu stroje, protože jinak by kvůli způsobu zpracování parametrů interpretem neprošlo přidané přesměrování výstupu příkazu ping. Tento výstup nás nezajímá, chceme pouze návratový kód. Použití může vypadat takto.

ara:~$ ruby rping.rb localhost
Dostupný
ara:~$ ruby rping.rb hurvinek
Nedostupný

Poněkud složitějším příkladem by mohlo být čekání na to, až bude daný stroj dostupný a vyvolání nějaké akce, jakmile se to stane. Jednoduchá verze (a v určitém ohledu chybná!) takového skriptu je zde.

loop do
  available = system(„ping -c1 #{ARGV[0]} > /dev/null 2>&1“)
  if available
    puts ‚Stroj je dostupný‘
    # akce
    break
  end
  sleep 60
end

Tento program běží po spuštění v nekonečné smyčce dané metodou loop a blokem, který ji předáváme. Pokud dojde k tomu, že je stroj dostupný, vykoná se požadovaná akce a smyčka se opustí příkazem break. V opačném případě se čeká zadaný počet vteřin pomocí volání metody sleep a následuje další cyklus. V tomto jednoduchém návrhu se ale skýtá potenciální nekonečný běh programu. Nerozeznáváme totiž situaci, kdy spuštění ping skončí chybou z nějakého jiného důvodu, než že cílový stroj je nedostupný. Například, pokud se nepodaří převést zadané doménové jméno na IP adresu, bude v proměnné available stále hodnota false a program nikdy neskončí. Zde by nám pomohlo další testování proměnné $?. Návratové kódy příkazu ping nalezneme v dokumentaci nebo si je otestujeme.

irb(main):002:0> system(‚ping -c1 localhost‘)
PING localhost (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 codel=64 time=0.119 ms
— localhost ping statistics —
1 packets transmicodeed, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.119/0.119/0.119/0.000 ms
=> true
irb(main):003:0> $?
=> #<Process::Status: pid=26444,exited(0)>
irb(main):004:0> system(‚ping -c1 x‘)
ping: cannot resolve x: Unknown host
=> false
irb(main):005:0> $?
=> #<Process::Status: pid=26446,exited(68)>
irb(main):006:0> system(‚ping -c1 hurvinek‘)
PING hurvinek.doma (192.168.1.23): 56 data bytes
— hurvinek.doma ping statistics —
1 packets transmicodeed, 0 packets received, 100.0% packet loss
=> false
irb(main):007:0> $?
=> #<Process::Status: pid=26449,exited(2)>

Čili na mém počítači je kód 0 v případě dostupnosti cílového počítače, 2 v případě nedostupnosti a 68 v případě nemožnosti převést doménové jméno. V proměnné je objekt. Pokud se chceme dostat skutečně jen k číslu návratového kódu, musíme použít $?.exitstatus. Doplnění kontroly na návratový kód ponecháme jako cvičení.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

1 Příspěvěk v diskuzi

  1. Možná to autor někde v předchozím dílu napsal, ale opakování matka moudrosti, takže si pamatujte, že string v ‚apostrofech‘ se bere jako jednoduchý řetězec a jsou povoleny maximálně zpětná lomítka, zatímco řetězce v „uvozovkách“ jsou určeny k dalšímu zpracování tj. #{VAR}..

    Já jsem samozřejmě použil system(‚ping -n 1 #{ARGV[0]} > NUL‘) ;-D

Napsat komentář: foo Zrušit odpověď