Třetí série dvacátého sedmého ročníku KSP


Zadání úloh

Letos se v jednotlivých sériích ohlížíme za zajímavými programátorskými chybami, a nejinak tomu bude i dnes. V předchozích sériích jsme viděli dělení nulou, ale také zrádnou chybu vzniklou převodem mezi celým číslem a floatem. Dnes nás oproti tomu čeká chyba, která vznikla zejména lidským přehlédnutím a strojovou kontrolou by byla těžko odhalitelná.

Také již opustíme válku v Perském zálivu a přesuneme se o několik let v čase, do doby, kdy většina z vás už byla na světě. Dnešní chyba ani nebude mít tak tragické následky, za oběť jí padlo „pouze“ několik set milionů dolarů. Teď se ale pojďme podívat do září 1999 na Patrickovu leteckou základnu.

* * *

James zaujatě pozoroval jednu z fotografií na zdi, zatímco hučení za ním sílilo. Když se ozvalo charakteristické cvaknutí oznamující, že voda je uvařená, vzal rychlovarku a zalil si kávu. Kuchyňkou se rozlila typická vůně.

Vyzbrojený milovaným nápojem se James vrátil do řídicí místnosti, kde se přidal ke svým kolegům navigátorům. Teď neměli mnoho práce, ale už za pár dní budou jejich znalosti velmi potřeba. Blížil se totiž čas, kdy Mars Climate Orbiter vstoupí na oběžnou dráhu Marsu.

Malé pozdvižení se ovšem dostavilo mnohem dříve. Na Zemi dorazila první fotografie Marsu. Pravda, obraz byl zkomprimovaný a možná patřičně nepřesný, ale navigátoři hned začali zkoumat, jestli na něm neobjeví vhodné místo k přistání. Po Mars Climate Orbiter, který má zkoumat atmosféru Marsu z jeho oběžné dráhy, totiž přijdou další sondy, a ty již budou na Rudé planetě přistávat.


Teoretická úloha27-3-1 Plocha k přistání (14 bodů)


Na Zemi dorazila fotografie zkomprimovaná do kvadrantového kódu. Nás zajímá, jaké místo na ní by bylo nejvhodnější k přistání, to znamená, kde je největší souvislá plocha.

Kvadrantový kód se používá pro dvoubarevné obrázky. Funguje tak, že se obraz nejprve rozdělí na čtvrtiny, které se postupně zakódují (pořadí kódování čtvrtin je „po řádcích“). Má-li celá plocha stejnou barvu (či je již tvořená jen jediným pixelem), zakóduje se jako jedno číslo (1 pro černou nebo 0 pro bílou barvu), v opačném případě se zpracovává rekurzivně.

Příklad takového kvadrantového kódu, který vznikl zakódováním z dvoubarevného obrázku, připojujeme níže. Tento zápis kvadrantového kódu je konzistentní s pátou úlohou, která ho také využívá.

1 1 0 0
1 1 0 0   ==>  (10(1100)(1010))
1 1 1 0
0 0 1 0

Vaším úkolem je v kvadrantovém kódu najít největší souvislou bílou oblast. Za sousední pixely považujeme jen ty, které spolu sousedí hranou (roh nestačí). Počítejte s tím, že se rozkódovaný obraz nevejde do paměti (tedy převést kvadrantový kód na obrázek a hledat oblast až v něm správné řešení není).

Poznámka: Kvadrantový kód funguje pěkně pro čtvercové obrázky o hraně délky nějaké mocniny dvou, ale dá se obdobně definovat i třeba pro obdélníkové obrázky. Protože to ale nepřináší nic nového, omezíme se v řešení úlohy jen na čtvercové obrázky o hraně délky mocniny dvou.

Řešení

James po chvíli nechal své kolegy dál zkoumat a sám se ponořil do vzpomínek …

* * *

Když bylo v srpnu 1993 jen těsně před vstupem na oběžnou dráhu ztraceno spojení se sondou Mars Observer, a tím podstatně oddáleny šance na bližší poznání Rudé planety, byl to šok, zvlášť pro Jamese a jeho tým.

Netrvalo ale dlouho a začaly se připravovat nové mise. Problém vesmírných misí ovšem je, že stojí spoustu peněz, které na ně musí někdo přidělit. Za Jamesem brzy přišel šéf, že bude potřeba napsat žádost o grant. Naštěstí tehdy dobře věděli, na co jednotliví členové komise, která bude o schválení rozhodovat, slyší; mohli jim tedy napsat návrh na míru. Zajímalo je ale, jakou mají vlastně konkurenci.


Teoretická úloha27-3-2 Návrhy pro komisi (12 bodů)


Je potřeba podat návrh komisi a nás by zajímalo, kolik různých návrhů komise schválí. Komise má C členů, kteří všichni sami za sebe rozhodují o schválení návrhu. Jako celek pak komise návrh schválí, pokud ho schválí alespoň K jejích členů.

Ilustrace: Robotický pomocník

Návrhy jsou ovšem dlouhé a členům se nechce číst je celé. Každý člen má proto nějaký seznam slov, která se mu líbí, a schvaluje právě ty návrhy, které začínají některým z jeho oblíbených slov. Návrhy i oblíbená slova jsou řetězce složené z malých písmen anglické abecedy a navíc panuje dohoda, že každý správný návrh má délku právě D písmen.

Na vstupu tedy dostanete počet členů komise a pro každého z nich jeho oblíbená slova. Dále dostanete počet členů nutných ke schválení a přijatelnou délku návrhů D. Vaším úkolem je zjistit, kolik různých návrhů (tvořených jen z malých písmen anglické abecedy) může komisí projít jako schválené.

Zajímá nás jen počet těchto návrhů, nemusíte je generovat. Navíc se nemusíte zabývat tím, že se vám toto číslo nevejde do běžné číselné proměnné (toto není úloha na velká čísla).

Příklad: Uvažme trojčlennou komisi, ve které jsou potřeba alespoň dva její členové ke schválení návrhu, a návrhy délky čtyř písmen. Oblíbená slova jednotlivých členů vyjadřuje tabulka níže.

  1. člen: pes psa
  2. člen: psal kun
  3. člen: pest ps

Je jasné, že návrh musí začínat na p, jinak by ho neschválili alespoň dva členové (na slovo kun tedy můžeme zapomenout). Možnosti, které nám zbývají, jsou tedy buď pest, nebo psaX, kde X může být libovolné písmeno (všimněte si, že třeba psbX už je přijímané jen jedním členem komise, psal všemi a psat alespoň dvěma).

Možností je tedy dohromady 26 + 1 = 27.

Řešení

Snad právě proto, že znali preference jednotlivých členů komise, nebylo pro Jamesův tým těžké peníze získat. Po vyřešení finanční otázky ovšem přišly na řadu otázky další, techničtější a v mnohém složitější.

Většinu konstrukčních záležitostí řešila společnost Lockheed Martin, se kterou NASA uzavřela smlouvu na výrobu sondy, přesto občas některé řešené problémy probublaly i k Jamesovi. K těm zajímavějším patřila konstrukce antény.

Jednou z klíčových vlastností každé vesmírné sondy je totiž schopnost komunikovat s lidmi na Zemi. Od začátku bylo jasné, že na straně Země se k tomuto účelu využije síť Deep Space Network, která byla na Zemi vybudovaná již koncem šedesátých let a využívá se pro komunikaci s jinými sondami.

Aby mohla sonda do této sítě posílat informace, musí být ovšem vybavená dostatečně silnou anténou. Taková anténa se skládá z mnoha vysílačů, jejichž volba byla trochu oříšek. Tím spíš, že ač peníze byly, plýtvat se jimi nemohlo.


Teoretická úloha27-3-3 Výběr vysílačů (13 bodů)


Anténa vesmírné sondy má stromovou strukturu, přičemž v každém uzlu se nachází nějaký vysílač. Kvůli rušení ale v žádných dvou sousedních uzlech nesmí být vysílače stejných typů.

Různé vysílače mají různou cenu, i-tý typ vysílače stojí 2i dolarů (číslujeme od 0). Na vstupu dostanete popis antény, tedy který uzel sousedí s kterým. Určete, kolik nejméně dolarů bude stát umístění vysílačů do všech anténních uzlů.

Příklad: Na anténě níže vidíte, že v tomto případě je nejvýhodnější použít tři typy vysílačů (s cenami 20, 21, 22 neboli 1, 2, 4). Použít jen dva typy vysílačů by v tomto případě vyšlo dráž.

Příklad rozložení vysílačů

Lehčí variantaLehčí varianta (za 3 body): Jako součást řešení vymyslete nějaký rozumně malý příklad antény, na které je potřeba použít čtyři různé druhy vysílačů, aby výsledná cena byla co nejmenší.

Rozumně malým příkladem nemyslíme nutně, aby měl co nejméně vrcholů to jde, ale spíše aby byl rozumně jednoduše zkonstruovatelný (jednoduchý popis konstrukce je lepší než obrovský obrázek o tisíci vrcholech).

Řešení

* * *

Uběhlo několik dní od chvíle, kdy Jamese hlas jednoho z jeho kolegů vytrhl ze vzpomínání a vrátil do reality. To navigátoři museli vyměnit hledání souvislé oblasti na fotografii za počítání, kontrolování, konzultování, nové počítání a tak stále dokola. Sonda se totiž rychle blížila k Marsu a bylo třeba navést ji na takovou dráhu, z které se dostane do správné výšky nad povrchem planety.

Ještě ten den spočítali vše potřebné. O týden později, ve středu 15. září 1999, byl provedený čtvrtý manévr upravující trasu letu. Očekávalo se, že až se sonda 23. září dostane do blízkosti Marsu, bude se nad jeho povrchem nacházet ve výšce 226 kilometrů. Teď, tři dny před očekávaným vstupem na oběžnou dráhu, ovšem navigátorům vycházelo, že při zachování trajektorie bude výška mnohem menší.

James se zamračil na obrazovku počítače. Pak rychle něco naťukal do kalkulačky, kterou měl položenou před sebou, ale stále mu vycházelo málo. 158. 158 kilometrů nad povrchem Marsu místo očekávaných 226. To bylo o dobrou třetinu méně. Zatím to nebylo kritické, Mars Climate Orbitter by měl s patřičnou úpravou oběžné rychlosti přežít ještě ve výšce 80 kilometrů, ale komu by se líbilo, když se realita takovým způsobem liší od očekávání? James se navíc děsil, že další den vyjde ještě méně.

Přitom od počátku mise probíhala dobře …

* * *

Psal se 11. prosinec 1998 a spousta lidí v čele s konstruktéry a navigátory sledovala start nosné rakety Delta II, která měla Mars Climate Orbiter dopravit na Hohmannovu elipsu.

Mezi sledujícími James pochopitelně nemohl chybět, ačkoliv on kromě rakety důsledně sledoval i lecjaké naměřené údaje. Po odpočtu, během kterého ještě víc vystoupalo očekávání všech zapojených, byly zažehnuty motory. Objevil se jasný záblesk, který přešel v ohnivou čáru, a Delta II vystřelila vstříc modrému nebi, a ještě dál.

Tak začala 669 milionů kilometrů dlouhá cesta sondy, která měla odpovědět na mnoho otázek pozemšťanů.

Let probíhal dobře, jen v jedné chvíli navigátoři zvažovali, zda by se nevyplatilo nechat sondu chvíli poletovat tam a zpět, aby její solární panely nasbíraly co nejvíc energie.


Praktická CodExová úloha27-3-4 Doplňování energie (12 bodů)


Sonda prolétá vesmírem, kde některými místy prochází výjimečně silné sluneční paprsky. Solární panely dokáží z těchto paprsků získat energii, ovšem na přelet mezi místy vzdálenými i spotřebuje sonda i jednotek energie. Navíc odpadní látky zastíní paprsek, takže z jednoho místa lze energii čerpat pouze jednou.

Na vstupu dostanete popsáno, jaké množství energie se nachází v jednotlivých místech, a vzdálenosti mezi těmito místy. Dále dostanete určený výchozí bod, na kterém se sonda nachází. Určete, s jakou největší energií může sonda skončit.

Například pro situaci níže (horní čísla představují množství energie, dolní vzdálenosti mezi místy) se začátkem ve třetím bodě může sonda skončit maximálně se 64 jednotkami energie. Nejlepší řešení se z výchozího místa vydá těmito přelety: LRRLLLRRRR (L – doleva, R – doprava).

Příklad rozmístění energie

Tato úloha je praktická a řeší se ve vyhodnocovacím systému CodEx. Přesný formát vstupu a výstupu, povolené jazyky a další technické informace jsou uvedeny v CodExu přímo u úlohy.

Řešení

* * *

Jamesovy obavy nebyly plané, během dalších dvou dní klesla očekávaná výška, v které by sonda měla k planetě přiletět, o dalších 50 kilometrů. To by ale ještě stále mělo stačit. A dál už očekávaná výška klesat nemohla, chvíle, kdy Mars Climate Orbiter vstoupí na oběžnou dráhu Marsu, již byla na dosah.

Právě proto bylo v řídicí místnosti rušno jako málokdy, přestože ještě nebyly ani čtyři hodiny ráno. Blížil se jeden z převratných okamžiků kosmonautiky.

Stále nebylo jasné, proč se očekávání a realita tak rozchází. Výška, ač aktuálně odhadovaná na málo přes 100 kilometrů nad povrchem Marsu, ovšem dostačovala a vstup na oběžnou dráhu byl zahájen. Sonda složila své solární panely, vhodně se vůči planetě natočila a zažehla hlavní motor.

Ve čtyři hodiny a čtyři minuty bylo spojení se sondou zničehonic přerušeno. Navigátoři si vyměnili několik vyděšených pohledů. Snažili se obnovit kontakt, ale nedařilo se.

Ani ne o dvě minuty později měla sonda navíc vstoupit do zákrytu Marsu, kdy by tak jako tak nebylo možné s ní komunikovat. Nedaří se navázat spojení, jen protože je sonda v zákrytu, nebo protože se stalo něco mnohem ošklivějšího?

Jamesovi padl pohled na fotografii, která před dvěma týdny ze sondy dorazila. Tehdy to ještě šlo všechno skvěle!

* * *

Kdyby měla sonda lidské pocity, asi by se na své cestě dost nudila. Po zajímavém startu a troše poletování tam a zpět za světelnými paprsky již nic zajímavého nepřišlo. Zůstal jen dlouhý let černou tmou zpestřený pouze světly hvězd.

Po dlouhých devíti měsících sonda konečně doletěla na dohled Marsu. Ještě z velké dálky pořídila jeho fotografii, a protože na fotografii planety z vesmíru je mnoho tmavého místa, stejně jako mnoho světlého místa, rozhodl se počítač odeslat ji na zemi kvadrantisticky zkomprimovanou.


Praktická opendata úloha27-3-5 Komprese obrazu (10 bodů)


Sonda posílá snímek Marsu. Nejprve ho ovšem za pomoci ztrátové kvadrantistické komprese převede do kvadrantového kódu (popsaného v 27-3-1).

Při kvadrantistické kompresi se jedna čtvrtina obrazu prohlásí za celočernou, jedna za celobílou a zbylé dvě se zpracují rekurzivně. Pokud se rekurze dostane až na úroveň jednotlivých pixelů, může být už barva rekurzivních částí jakákoliv. Pro čtverec 2 ×2 ale ještě platí, že jedna jeho čtvrtina musí být celočerná, jedna celobílá a zbylé dvě libovolné. Pořadí kvadrantů je „po řádcích“.

Na vstupu dostanete původní obraz. Vaším úkolem je vypsat kvadrantový kód takové jeho kvandrantistické komprese, která se od původního obrazu liší v co nejméně pixelech.

Konkrétněji bude mít vstup podobu popisu obrázku ve formátu PBM. To je jednoduchý formát na ukládání černobílých obrázků.

Obrázek je v něm kódovaný po řádcích, vždy jedno číslo (1 nebo 0) na jeden pixel. Na řádku jsou mezi jednotlivými čísly mezery a na konci každého řádku se nachází znak nového řádku, nic jiného se zde nevyskytuje. Platný PBM soubor je také uvozen na prvním řádku znaky P1 a na druhém řádku mezerou oddělenými čísly udávajícími jeho šířku a výšku (v tomto pořadí).

Obrázky v této úloze budou pro jednoduchost vždy čtvercové o hraně 2K pixelů a jejich velikost nepřesáhne 1024×1024 pixelů.

Na výstup vypište nejprve na první řádek počet změněných pixelů a následně na druhý řádek kvadrantový kód kvadrantistické komprese.

Ukázkový vstup:
P1
8 8
1 1 1 1 1 1 1 0
1 1 1 0 1 1 1 0
1 0 0 0 1 1 0 0
0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0
1 1 1 1 0 0 0 0
1 1 1 1 0 0 0 0
0 0 0 0 0 0 0 0
Ukázkový výstup:
8
((1(1110)(1000)0)
 (1(1010)(1110)0)
 1
 0
)
Poznámka k příkladu: Pro přehlednost příkladu jsme druhý řádek výstupu rozlomili po jednotlivých kvadrantech, v reálném výstupu by vše od první do poslední závorky bylo na jediném řádku.
Ukázkový vstup:
P1
4 4
0 0 1 1
0 0 1 1
0 0 1 1
1 0 1 1
Ukázkový výstup:
1
(01(0010)(0111))

Toto je praktická open-data úloha. V odevzdávátku si necháte vygenerovat vstupy a odevzdáte příslušné výstupy. Záleží jen na vás, jak výstupy vyrobíte.

K prohlédnutí obrázku ve formátu PBM můžete využít na Linuxu např. program Eye of Gnome (eog), na Windowsech programy Irfan View nebo XnView. Na obou systémech si s PBM poradí i Gimp či OpenOffice Draw.

Řešení

* * *

Jamesova myšlenka, že tehdy to ještě šlo skvěle, se časem ukázala jako nepříjemně přesná. Mars Climate Orbitter se totiž navigátorům neozval nejen po dvaceti minutách, kdy se měl opět dostat mimo zákryt Marsu, ale ani po hodině, ani po dvou dnech.

Po těchto dvou dnech byla sonda oficiálně prohlášena za ztracenou a mise za neúspěšnou. Navigátoři zpětně spočítali, že sonda se ve skutečnosti dostala do výšky pouhých 57 kilometrů nad povrchem Marsu, kde ji zřejmě spálila atmosféra.

Ještě před oficiálním ukončením mise bylo zahájeno vyšetřování s cílem zjistit, co se vlastně stalo a proč se sonda pohybovala mnohem níž, než všichni očekávali.

James si rychle zvykl, že se teď kolem něj pohybuje mnohem víc lidí, že se zkoumá hned tu, hned ono. I jeho samotného zajímala příčina tohoto selhání a snažil se přijít věci na kloub.

Do místnosti právě vešel i jeden z techniků. „Hej, lidi, pomůžete mi někdo uklidit přepravky ve skladu?“ ptal se hned místo pozdravu. James usoudil, že trocha fyzické aktivity mu jen prospěje a přidal se k několika ochotným pomocníkům.

Ilustrace: Těžce pracující hroch

Teoretická úloha27-3-6 Ukládání přepravek (9 bodů)


Ve skladu je třeba uspořádat přepravky, a to tak, aby zabíraly co nejméně místa. Přepravky jsou kulaté, každá má svůj vnější a vnitřní průměr. Pokud je vnější průměr jedné přepravky menší než vnitřní průměr druhé přepravky, dají se vložit do sebe (a do nich případně ještě menší přepravka, vznikají tak jakési „komínky“).

Na vstupu dostanete vnitřní a vnější průměry všech N přepravek. Vaším úkolem je zjistit, do kolika nejméně komínků se dají uspořádat.

Řešení

„Tedy, tohle bude mít pěkných pár liber,“ prohlásil jeden z pomocníků zvedaje pořádný komínek mnoha přepravek.

„Cos to řekl?“ vytřeštil oči Jamesův kolega Thomas.

„Jen že je to těžké…“ bránil se pomocník.

Ostatní, včetně Jamese, Thomase jen nechápavě pozorovali.

„O to nejde. Jde o ty libry! A o to, že libry nejsou kilogramy,“ pokračoval vzdor nechápavým pohledům Thomas. „A taky o to, že kilogramy jsou to, co ta sonda očekávala.“

Ozvala se hlasitá rána. To Jamesovi z rukou vypadlo několik přepravek. A podle výrazů ostatních byla spíš náhoda, že se to samé nestalo více lidem.

* * *

Vyšetřování potvrdilo, že příčinou selhání byla neshoda v používaných jednotkách. Řídicí středisko ze Země odesílalo instrukce s imperiálními mírami, kdy sílu udávalo v silových librách. Sonda je ovšem očekávala v metrické podobě, tedy sílu čekala v Newtonech.

Jelikož silová libra je více než čtyřnásobek Newtonu, došlo při výpočtech k chybám, které byly pro úspěšnost vstupu na oběžnou dráhu fatální. Rozkol mezi očekávanou a naměřenou pozicí byl zaznamenaný a v týmu zodpovídajícím za let družice se uvažovalo o provedení ještě dalšího, pátého, manévru korigujícího dráhu, ten ale nebyl nikdy provedený.

Neúspěšnou misi s vámi sledovala

Karolína „Karryanna“ Burešová


Seriálová úloha27-3-7 UNIXové déjà vu (15 bodů)


Dnešní díl seriálu ve vás možná vyvolá pocit, že jste ho už někdy četli, ale ne tak docela. Vrátíme se totiž k mnohému z toho, co už umíte, a pronikneme ještě o kousek hlouběji. Připravte se na vydatnou porci povídání o nápovědě, o souborovém systému, uživatelích a právech, o řídicích strukturách a funkcích v shellu a o formátovaném výstupu.

Z předchozích dílů seriálu máte k dispozici shell, nejspíš Bash, umíte se v něm pohybovat po souborovém systému a zvládáte psát jednoduché skripty pro manipulaci s obsahem textových souborů. Také když zapomenete přepínače konkrétního příkazu, umíte si je v manuálových stránkách najít.

Umíte si ale pomoct, když zapomenete, jak se nějaký příkaz jmenuje?

Nápověda

Je nemožné si pamatovat všechny vlastnosti každého nainstalovaného programu, natož stíhat sledovat změny. Nápověda, manuál nebo dokumentace jsou základními prameny informací pro uživatele libovolného software a UNIX v tomto ohledu není výjimkou. Sebelepší informace je ale k ničemu, když ji neumíte najít.

Příkaz man už znáte. Napadlo vás podívat se na man man?

Zjistíte tam, že k hledání řetězců v popisech příkazů slouží přepínač -k. Pokročilejší možnosti nabízí utilita apropos.

Také narazíte na přepínač -s, jehož hodnotou je sekce manuálu a někdy jde dokonce přepínač vynechat a psát jen sekci (man 1 man). Počkat, co jsou sekce? Jsou očíslované, každá stránka je v nějaké zařazena a obvykle ji má uvedenou v závorce za svým jménem (např. cat(1), shells(5) nebo standards(7)). Konkrétní význam a číslování se liší, POSIXový standard (který si zmíníme dále) o nich nemluví vůbec. Pro představu se podívejme na Debian Linux:

1Spustitelné programy nebo příkazy shellu
2Systémová volání (funkce poskytované jádrem)
3Knihovní volání (funkce v programových knihovnách)
4Speciální soubory (obvykle nalézané v /dev)
5Souborové formáty a konvence (např. /etc/passwd)
6Hry
7Směs (včetně balíků maker a konvencí)
8Příkazy administrace systému (obvykle jen pro roota)
9Funkce jádra

V různých sekcích můžete najít stejnojmenné stránky. Například passwd(1) je utilita pro změnu hesla a passwd(5) (sekce 5 na Linuxu, jinde nejspíš jiná) dokumentuje databázi uživatelů /etc/passwd. Musíte buď sekci znát, nebo použít přepínač -a a postupně si prohlédnout všechny.

Když už konkrétní stránku máte, pořád není vyhráno. Může být dlouhá a nepřehledná. Pak vám pomůže váš stránkovač. Příkaz man by mohl vysypat horu textu přímo do terminálu se škodolibým úsměvem a slovy „poraďte si“, jako příjemnější se ale ukázalo, když pustí less nebo starší a standardní more a manuál vám ukáže v něm. V prvním dílu jsme vám prozradili, že se oba zavírají klávesou q (quit), přidáváme h (help) pro nápovědu, / (lomítko) pro vyhledávání (potvrdíte enterem) a n (next) pro vyhledání dalšího výskytu.

Aby to nebylo příliš jednoduché, k manuálu existuje alternativa: infostránky. Některé jsou mnohem obsáhlejší než odpovídající manuálová stránka a jsou členěné, nevypadají jako jeden dlouhý dokument. Jejich prohlížeč se jmenuje info, nápovědu v něm získáte napsáním otazníku, zbytek už zjistíte sami.

Bash k nápovědě přistupuje po svém. Na man bash najdete i popis jeho vestavěných příkazů, jako je cd nebo pwd, kdo by ovšem chtěl hledat jehlu v kupce sena? Vysvobodí vás jeho příkaz help. Mrkněte na help help, je vcelku intuitivní.

Pokročilejší z vás by mohlo zajímat, které utility a jejich přepínače mají být dostupné na všech UNIXech, ať už je to Gnu/Linux, Solaris, OS X nebo nějaká odnož BSD. Taková znalost slouží k psaní přenositelných skriptů, tedy skriptů, které budou fungovat i na jiném systému, než na kterém jste je napsali. Vaši zvědavost ukojí norma POSIX, které se certifikované UNIXy držet musejí a ty ostatní aspoň plus minus chtějí. Kdykoliv se budeme odvolávat na normu nebo POSIX, myslíme POSIX 2013. Na jeho stránce je vpravo dole odkaz ke stažení té kupky HTML stránek v jednom archivu, z neoficiálních zdrojů je možné sehnat POSIX i v podobě manuálových stránek. V Debianu je takovým zdrojem balík manpages-posix v repozitáři non-free.

Souborový systém

První díl seriálu se vás snažil nezahltit a nerozptylovat, o souborovém systému řekl jen to nejnutnější, minule jste nakoukli do práv souborů, když jste vytvářeli spustitelný skript. Je načase povědět o souborovém systému víc.

Ilustrace: Hroch pod stromem

Logicky je souborový systém jediný, s kořenem / („root“), fyzicky jich ale bývá víc, z nichž některé mohou sídlit třeba jen v operační paměti nebo dokonce na úplně jiném stroji. Všechny dostupné na aktuálním stroji si můžete prohlédnout příkazem df. Vypíše pro každý souborový systém do tabulky název, velikost, využití a kam v logickém souborovém systému je připojený. Často je k dispozici přepínač -T, se kterým df ukáže i typ souborového systému, a přepínač -h, se kterým vypíše obsazené a volné místo v lidsky čítelných jednotkách.

Prostor zabraný konkrétním souborem umí spočítat du. Pokud dostane adresář, rozpitvá statistiku na jeho položky, podobně jako je tomu u ls. Příkazu ls to můžete zakázat přepínačem -d, obdobně u du -s.

Nenechte se zmást tím, že df i du přemýšlejí v blocích. Je to dáno běžnou strukturou disků, soubor zabírající blok jen z části nemůže jeho zbytek přenechat jinému souboru, přebytečné místo zůstane nevyužité. Velikost bloku se obvykle liší mezi normou, utilitami a diskem, buďte tedy obezřetní a uvědomujte si, jaká velikost se u vás kde používá.

Příkaz ls s přepínačem -l zobrazuje velikost souboru v bajtech a na zabrané bloky se nijak neohlíží. Počet zabraných bloků nechá zobrazit přepínač -s. Celkovou velikost zabraných bloků v lidsky čitelných jednotkách ukazuje du -h.

Úkol 1 [1b]

Zjistěte, jak velké bloky používá váš disk a vaše utilita du, která by podle normy měla používat 512B bloky. Svá zjištění doložte použitými příkazy, jejich výstupy a popisem své úvahy.

Konkrétní použitý souborový systém s sebou nese svá omezení. Na Windows se kdysi používal formát FAT, později NTFS, v Linuxu jsou doma ext2 až ext4, v BSD a Solarisu ufs. Lišit se mohou maximální délkou jména souboru, maximální velikostí souboru, maximální využitelnou velikostí disku, povolenými znaky v názvech souborů, (ne)podporou ukládání různých metadat, …

Norma vyžaduje, aby souborový systém rozlišoval malá a velká písmena a názvy souborů neobsahovaly lomítko (oddělovač komponent cesty) a NUL (\0 v jazyce C, bajt s hodnotou 0). Jazyk C vznikl pod UNIXem a UNIX do něj byl po čase přepsán, jsou spolu dodnes hodně prolnuté. Když zakážeme znak NUL, máme zaručeno, že je možné název souboru považovat za řetězec jazyka C a používat na něm řetězcové funkce, např. strlen().

Výše uvedená omezení jsou dnes běžně opravdu jediná vynucená, všechno ostatní funguje. (Nepočítáme-li obskurní starožitný FAT, přežívající na některých flashdiscích.) Čímž neříkáme, že celý zbytek Unicode v názvech souborů najdete, nebo dokonce že můžete obskurními znaky soubory beztrestně pojmenovávat.

Díky absenci NUL v názvu souboru máme jisté, že když za sebe naskládáme jména souborů oddělená znakem NUL, budeme je umět opět jednoznačně rozdělit. Toho využívají některé běžné utility, bohužel pomocí nestandardních přepínačů. Tuto jistotu nám norma nedává, pokud použijeme jako obvykle znak LF (\n v C, „konec řádku“).

Proto se z bílých znaků používá jen mezera, LF v názvu naštěstí není běžné, tedy můžeme být v klidu. Kdo takovou zvyklost poruší, následky nechť si nese sám. Zkuste si nějaký soubor s LF v názvu vyrobit a pohrát si s ním!

Soubory s diakritikou, interpunkcí a mezerami v názvech se opravdu dají potkat, takže by s nimi vaše skripty měly umět pracovat. Pomohou vám k tomu znalosti z prvního dílu: escapování, uvozovkování a ve vzácném případě minusu na začátku názvu souboru parametr --.

Soubory systému a programů obvykle dodržují ještě mnohem striktnější omezení, než je to popsané výš. Volí jména souborů, která

  1. obsahují jen písmena velké a malé anglické abecedy, číslice, podtržítko, tečku a minus ([A-Za-z_.-]), a navíc
  2. nezačínají minusem (aby se nepletla s přepínači).
Soubory s tečkou na začátku jsou skryté před wildcardy a ls. Druhé běžné použití tečky je oddělení přípony od zbytku jména souboru, jinak se tímto znakem šetří.

Shell se hodí na jednoduché skripty spořící čas, ne na psaní neprůstřelných programů (to v něm ani dobře nejde). Pokud v něm budete pracovat víc, dojdete ke kompromisu mezi omezováním se a nutností uvozovkovat při běžné práci moc často. Vynecháte nejspíš běžné oddělovače (LF, mezeru, tabulátor, dvojtečku, středník a čárku), speciální znaky shellu ($`"'#!?*[]{}();|{\I }&), a dáte si pozor na minus na začátku názvu. Možná si navíc budete šetřit čas při psaní vynecháním diakritiky a velkých písmen, ačkoliv to zas tolik nepomůže. Důležité je hlavně trefit začátek slova a zbytek už zařídí doplňování tabulátorem.

Části cesty, přípony

Přípony. Ve Windows se podle nich točí svět, binárku bez přípony .exe spustíte těžko. UNIX se s nimi vypořádal jinak – přípony považuje za informaci pro uživatele, sám se řídí prvními několika bajty souboru, kde obvykle je „magic number“. Podle něj umí formát určit třeba i utilita file:

hroch@ksp:~$ file /etc/passwd
/etc/passwd: UTF-8 Unicode text
hroch@ksp:~$ file /usr/bin/vimtutor
/usr/bin/vimtutor: POSIX shell script text executable

U binárních programů toho file umí zjistit hodně. Kdybychom ho neměli, museli bychom binárku prohlížet nějak ručně a třeba si všimnout toho, že na začátku jsou znaky DEL, E, L a F, přičemž ELF je jméno formátu spustitelných souborů pro UNIX.

hroch@ksp:~$ od -c -Ax -tx1 -N10 /bin/sh
000000 177   E   L   F 002 001 001  \0  \0  \0
        7f  45  4c  46  02  01  01  00  00  00
00000a

Zkuste si sami pomocí od nebo rozšířeného, ale nestandardního hd prohlédnout nějaký obrázek PNG, dokument PDF, … Pokud chcete být drsní, vynechte u od přepínač -c a ve vedlejším terminálu si otevřete man ascii. ;-)

Příponu tedy běžně není potřeba od zbytku jména souboru oddělovat, obzvlášť u textových a spustitelných souborů často ani žádná přípona použita není. Zato bychom někde ve skriptu mohli chtít získat zvlášť jméno souboru a zbytek cesty. Poslouží nám příkazy basename a dirname:

hroch@ksp:~$ dirname /usr/bin/less
/usr/bin
hroch@ksp:~$ basename /usr/bin/less
less
Jejich opakovaným použitím můžete rozebrat cestu na jednotlivé komponenty.

Typy souborů

Když už jsme nakousli soubory v UNIXu, podívejme se na ně blíž. UNIXová filosofie se totiž drží zásady, že skoro všechno je soubor. Běžné textové soubory nebo soubory s binárními daty (fotky, videa, …) nás asi nepřekvapí. Ale UNIX jako soubory reprezentuje i takové věci jako vstup z klávesnice (systém odtud čte po znacích) nebo výstup do zvukové karty. Podstatné je, jak se který soubor chová při používání.

Soubor je na disku typicky reprezentován jedním inodem, každý z nich má v rámci souborového systému svoje unikátní číslo. Uvnitř mezi dalšími metadaty systém ukládá informace o právech a vlastnících souboru, jeho typ a velikost, počítadlo odkazů (viz dále) a hlavně odkazy na jednotlivé datové bloky se samotným obsahem.

Je důležité, že jméno souboru si nepamatuje sám soubor, ale pamatuje si ho nadřazený adresář (což je jen speciální typ souboru). Inode reprezentující adresář obsahuje ve své datové části jména a příslušná čísla inodů pro všechny v něm obsažené soubory.

V předchozích dílech jsme se věnovali jen dvěma typům souborů: běžným souborům a adresářům. Dalšími jsou již zmiňovaná vstupní a výstupní zařízení, která sídlí hlavně v adresáři /dev a dělí se na bloková (disk) a znaková (terminál). V neposlední řadě se hodí vědět o rourách.

Zatím jsme potkali jen roury anonymní, které shell natahuje mezi dvěma příbuznými (společně spouštěnými) procesy: ls -l /bin | head. Mezi nepříbuznými procesy (spouštěnými třeba i dvěma různými uživateli) anonymní rouru natáhnout nejde, ale oba mohou znát cestu k pojmenované rouře. Jeden z ní čte, druhý do ní zapisuje a jméno potřebují jenom k tomu, aby ji mohli otevřít. A kde pojmenovanou rouru sebereme? Vytvoří ji příkaz mkfifo.

Ilustrace: Hroši a roury

V shellu je bezesporu nejpoužívanějším speciálním souborem /dev/null neboli „černá díra“. Je to ideální místo, kam zahazovat věci, které na nic nepotřebujeme. Můžeme ho využít, pokud nás nezajímá standardní výstup příkazu, a jde nám jenom o jím vyrobený soubor nebo jeho návratovou hodnotu. Pokud bude příkaz chtít vstup a my mu budeme chtít dát prázdný soubor, /dev/null je také vhodné použít.

echo Windows > /dev/null
cat /dev/null

Podobně se chová /dev/zero, až na to, že při čtení dodává nekonečně dlouhou posloupnost znaků NUL. Soubor délky 1024 bajtů vytvoří head -c 1024 /dev/zero > soubor. Pěkné hraní je i se soubory /dev/random a /dev/urandom.

Někdy přesměrujeme našemu skriptu výstup do souboru, a přesto bychom chtěli vybrané informace o jeho provádění vidět na terminálu. Může nám je ukazovat tak, že je bude zapisovat do /dev/tty, který reprezentuje aktuální terminál. Podobně když máme přesměrovaný vstup a chceme z terminálu (z klávesnice) číst.

(
	echo 'Potvrdte "ano":' > /dev/tty
	read odpoved < /dev/tty
	[ "$odpoved" = ano ] || exit 1
	cat
) < soubor1 > soubor2

Málem bychom zapomněli… Ve výstupu ls -l poznáte jednotlivé typy souborů podle prvního znaku na řádku, ještě před právy. Kdyby nějaké písmeno nebylo jasné, v manuálu ls jsou zkratky vysvětleny. Minus je běžný soubor.

Odkazy v souborovém systému

Občas se nám hodí pořídit si zkratku, rychlý odkaz na nějaký soubor či adresář. Na takové věci se v UNIXu využívají hardlinky (linky) a symlinky.

Hardlink je odkaz, který „natvrdo“ ukazuje na stejný inode jako odkazovaný soubor. V adresáři je pod jménem linku uloženo přímo číslo inode, na který odkazuje. Při vytvoření linku se v daném inode zvedne počítadlo odkazů, při jeho smazání se zase sníží, a když dojde na nulu, odstraní se i samotná data. Výjimkou jsou otevřené soubory – dokud nějaký proces má soubor otevřený, souborový systém přepsání jeho dat neumožní. Tak může mít proces bezpečně otevřený i soubor, který už nemá žádné jméno.

Po vytvoření hardlinku jsou původní a nový soubor od sebe nerozeznatelné, ale nese to s sebou několik omezení. Předně musí všechny linky sídlit na stejném diskovém oddílu, kde jsou uložena samotná data, a také nejde vytvořit hardlink na adresář, jen na soubor. (Kdo by se vyznal v souborovém systému, kde může být adresář umístěný sám v sobě?) Při přesunutí (přejmenování) nebo vymazání původního souboru bude hardlink ukazovat stále na původní verzi. Protože mohou být hardlinky někdy zrádné, doporučujeme pro běžnou práci používat spíše symlinky.

Symlink, neboli symbolic link, je jen jednoduchý zástupce, který ukazuje na nějakou cestu v souborovém systému (na libovolném připojeném oddílu a na libovolný soubor či adresář). Ve skutečnosti vypadá skoro jako malý textový soubor, který má v sobě zapsanou absolutní (začínající lomítkem) či relativní cestu ke svému cíli.

Při vymazání nebo přesunutí původního souboru přestane symlink fungovat, pokud neobsahuje relativní cestu a není přesunut spolu s cílem. Ukazuje totiž na cestu, ne na konkrétní soubor. Pokud cílový soubor smažeme a nahradíme novým, bude symlink ukazovat na tento nový soubor.

K symlinkům je třeba dodat dvě varování, abyste se nedivili a nedomýšleli si něco, co není pravda.

  1. Předně, symlink a zástupce z Windows se chovají zásadně jinak. Zástupci jsou běžné soubory, podobné .desktop souborům na Linuxu, jen jsou binární.
  2. Druhé varování se týká výstupu ls -s. Na mnoha systémech bude u symlinků ukazovat nulový počet zabraných bloků. Pokud je totiž symlink dostatečně malý, není problém ho celý uložit přímo uvnitř jeho inode. Jak úsporné! :-) Říká se tomu inlining a nové souborové systémy (třeba ext4) umí toto chování zapnout i u jiných typů souborů.

Hardlinky a symlinky se vytvářejí příkazem ln. Bez přepínače vyrobí hardlink, s přepínačem -s symlink:

ln cesta/k/souboru novy_hardlink
ln -s cesta/k/souboru relativni_symlink
ln -s /cesta/k/souboru absolutni_symlink

Cíl symlinku běžně vyčtete z ls -l:

lrwxrwxrwx 1 hroch users 3 8. pro 08:00 zdroj -> cil

Ve skriptech se nám bude hodit spíš příkaz readlink, který vypíše jenom cíl odkazu. Také má užitečný přepínač -f, se kterým vypisuje absolutní cestu získanou z argumentu nahrazováním všech symlinků na cestě skutečnými cestami, kam ukazují. Příkazu readlink -f tedy má smysl dávat nejenom symlinky, ale i běžné soubory.

Pokud budete uvnitř adresáře, do kterého jste se dostali přes symlink, můžete si plnou cestu k němu nechat vypsat pomocí pwd -P, přepínač -P zajistí rozepsání všech symlinků v cestě. Chová se stejně jako readlink -f . (všimněte si použití tečky).

Úkol 2 [2b]

Pusťte si ls -ld na nějaký adresář a všimněte si, kolik má hardlinků. Odkud vedou?

Úkol 3 [2b]

Vysvětlete, jak se v souvislosti s hardlinky liší

cat </dev/null >soubor
a
rm soubor
cat </dev/null >soubor

Uživatelé, skupiny, vlastníci a práva

Teď už máme představu o tom, co všechno v souborovém systému můžeme najít. Zatím jsme v něm ale beznadějně sami. Sotva přijdou další uživatelé, potřebujeme před nimi občas něco schovat, nenechat je měnit naše soubory, … Tedy budeme potřebovat systém oprávnění, který jsme v minulém dílu načali právem ke spuštění. Vůbec jsme ale nemluvili o tom, komu chmod +x právo ke spuštění přidělí.

UNIX je odpradávna víceuživatelský systém. Oddělené účty uživatelů a dodržování principu minimálních oprávnění mu zajišťují slušnou míru bezpečnosti. Soubor /etc/passwd obsahující databázi uživatelů už jste potkali, jeho dokumentaci najdete na Linuxu v man 5 passwd, jinde musíte hledat v jiné sekci. Možná vás zajímalo, kde se ukládají hesla – pak vězte, že v dřevních dobách to bylo opravdu tam, ale moderní UNIXy mají na citlivé údaje oddělenou databázi jinde v adresáři /etc. Hesla se navíc ukládají zakódovaná. Na Linuxu vám víc prozradí man shadow.

Vžijte se na chvíli do role administrátora školního UNIXového serveru. Máte na něm účty desítek, možná i stovek uživatelů a chcete jim přidělit nějaká oprávnění (jaká, k tomu se dostaneme později). Chtělo by se vám u každého zvlášť přemýšlet, co smí a nesmí? Asi byste si všimli, že studentské účty mají mít obecně jiná oprávnění než účty učitelů, že dokumenty k maturitnímu plesu jedné třídy nemá co upravovat nikdo z jiných tříd, tedy pokud nemají dvě třídy ples společný, atd. Přáli byste si, abyste mohli systému o třídách a učitelích říct a on vám dovolil oprávnění přidělovat hromadně.

Přání se vám splnilo, řešení má jméno skupiny. Jsou to pojmenované množiny uživatelů, je jim možné nastavovat společná práva, jejich databáze sídlí v /etc/group a více si o ní můžete přečíst v man group. V /etc/passwd má každý uživatel navíc skupinu, pod kterou se přihlašuje, neboli login group. Do ní automaticky patří.

Co znamená, že se uživatel pod nějakou skupinou přihlašuje? Inu, tato skupina je skupinovým vlastníkem jím vytvářených souborů. Co to pro ně znamená?

Když se podíváte na výstup ls -l, uvidíte řádky jako:

-rwxr-x--x 1 hroch users 42 8. pro 08:00 soubor

Uživatelským vlastníkem souboru soubor je hroch, skupinovým je users. Pokud se řekne jenom vlastník, myslí se uživatelský vlastník. Nenechte se zmást, až někde uvidíte hroch v obou sloupcích – skupiny a uživatelé se mohou jmenovat stejně a na některých systémech se takto pro každého uživatele zakládá jeho vlastní login group.

Ilustrace: Hroch uživatel

Na uživatelského vlastníka (user, owner) se vztahuje jiné nastavení oprávnění než na toho skupinového (group, group owner), a na toho zase úplně jiné než na všechny zbývající uživatele (others, world) kromě uživatele root.

Root, nebo také superuživatel, je takovým místním bohem. Smí všechno. Jeho oprávnění je potřeba např. k zakládání nových účtů nebo ke změně vlastníka souboru. Změnit skupinu souboru na některou ze skupin, ve které je členem, může ale i vlastník souboru.

Tolik pravomocí s sebou nese i hodně zodpovědnosti a moc velký průšvih, když se k účtu roota dostane někdo nepovolaný. Přestože se hledají alternativní cesty, jak si poradit bez superuživatele, root s námi ještě nějaký čas bude.

Pomocí příkazu su můžeme pouštět příkazy rootovým jménem, nebo i jménem jiného uživatele, pokud známe jeho heslo. Pokud si pustíme rootovský shell, bude mít v promptu místo dolaru mřížku (#). Alternativou su je příkaz sudo, který nám dovoluje pouštět příkazy pod jiným vlastníkem po zadání k tomuto účelu speciálně nastaveného hesla.

Pokud root zrovna nejste a snažíte se přistupovat k souboru, jaká pro vás platí oprávnění? Ve výše uvedeném příkladu

Tento symbolický zápis práv není až tak spletitý, jak na první pohled vypadá. První pozice v každé trojici je Read (čtení), druhá Write (zápis), třetí eXecute (spuštění). Minus znamená, že právo není přiděleno. Při čtení práv ve výstupu ls -l nezapomeňte, že těsně před nimi je typ souboru. Připomínáme, že minus znamená běžný soubor.

Často se používá ještě druhý způsob zápisu práv, numerický, nebo také oktalový, tedy pomocí osmičkové číselné soustavy. Práva vlastníka, skupinového vlastníka a ostatních v něm jsou reprezentovaná třemi osmičkovými ciframi. Jedna osmičková cifra má tři bity. 4 = r, 2 = w, 1 = x. Skládání se dělá bitovým součtem (or). 000 jsou tedy doslova nulová práva, 777 všechno všem, 700 všechno vlastníkovi, 755 všechno vlastníkovi a ostatním jen čtení a spouštění, 640 čtení a psaní vlastníkovi, čtení skupině a nic ostatním.

U běžných souborů jsou práva téměř intuitivní. Drobné zádrhele mohou být, že interpretované programy potřebují kromě hashbangu (např. #!/bin/bash, vizte předchozí díl seriálu) a práva ke spuštění i právo ke čtení. Interpret se k textu programu holt nějak musí dostat. U binárek problém není. Naopak binárky nespustíte bez práva ke spuštění, kdežto u skriptu v Bashi si snadno poradíte zavoláním bash skript.sh.

Větší potíže bývají s významem práva k zápisu. My jsme si ale už vysvětlili, jak fungují linky na soubor a že data souborů a záznamy v adresářích jsou na sobě do značné míry nezávislé, takže to máme snazší. Právo k zápisu mluví u souboru o zápisu do jeho dat. K přejmenování nebo smazání se vztahuje právo zápisu do adresáře. Přesto utilita rm váhá, když má mazat soubor, který nemá právo k zápisu. Je potřeba smazání ručně potvrdit nebo předem přidat přepínač -f (force – „Na nic se neptej a maž!“).

Práva souboru se ukládají s jeho inode – pokud je změníme přes jeden link, projeví se změna i ve všech ostatních. Zde se hodí třetí varování k symlinkům: práva u nich nemají smysl. Symlink se často chová transparentně a rozhodující jsou práva cílového souboru. Nenechte se zmást tím, že ls mu přisuzuje práva 777.

U adresářů jsme už právo k zápisu vyřídili o dva odstavce výš. Právo ke čtení adresáře potřebují ke své správné funkci wildcardy, ls, doplňování tabulátorem a vůbec cokoliv, co potřebuje vidět obsah adresáře. Právu x se u adresářů říká search (prohledávání). Dovoluje nám se znalostí jména souboru přistoupit k jeho obsahu, tedy při hodně nízkoúrovňovém pohledu vlastně přečíst z adresáře číslo inode souboru, pokud dodáme jeho jméno.

Bez práva ke čtení, ale s právem k prohledávání můžeme jaksi „poslepu“ pracovat s obsaženými soubory známých jmen, a v závislosti na právu k zápisu je můžeme i vytvářet a mazat. S právem ke čtení, ale bez práva k prohledávání můžeme obsah adresáře jenom vypsat, a to ještě mizerně. U každé položky uvidíme v ls -l jen jméno (a na některých souborových systémech i typ), ostatní metadata jsou uvnitř inode a místo nich se zobrazí jen otazníky. Ani nám pak nebude vadit, že bez práva k prohledávání nemůžeme nastavit adresář jako svůj pracovní (cd adresar).

Když už víme, k čemu práva jsou, jak je změnit? Příkaz chmod už jsme párkrát zmínili. Teď už navíc budete rozumět jeho manuálu, kde se můžete dočíst pikantní podrobnosti o právech včetně těch, které se sem nevešly.

Příkaz chmod může pracovat s právy zadanými oktalově i symbolicky. Se symbolickými právy umí nastavovat i jednotlivá práva při zachování ostatních. Kombinací je docela hodně, uveďme tedy jen příklad: chmod u+x,go-rw soubor přidá uživateli právo ke spuštění a skupině i ostatním sebere práva ke čtení a k zápisu, ostatní práva nechá netknutá.

Vlastníka umí měnit příkaz chown, skupinového chgrp. Příkaz chown navíc umí měnit skupinového i uživatelského vlastníka najednou, stačí je zadat oddělené dvojtečkou:

ksp:~# ls -l s; chown palec:ksp s; ls -l s
-rw-r--r-- 1 hroch users 1 8. pro 08:00 s
-rw-r--r-- 1 palec ksp   1 8. pro 08:00 s

Pokud u chown vynecháme uživatele před dvojtečkou, změní jen skupinového vlastníka. Příkazy chmod, chown i chgrp mají přepínač -R, který je nechá změnu aplikovat na daný adresář a rekurzivně na všechen jeho obsah.

Když už soubory existují, poradit si s nimi umíme. Teď si povíme, s jakými právy a vlastníky se zakládají.

Stejně jako soubor, i každý proces má vlastníka. Vlastník procesu se použije jako vlastník souborů tímto procesem vytvářených. Se skupinovým vlastníkem je to složitěšjí, ten se občas může dědit od adresáře. Přesný algoritmus výběru závisí na systému a jeho nastavení. Náš shell má jako vlastníka nás a jako skupinového vlastníka naši login group. Procesy, které spouští, tyto vlastníky zdědí. Všechny „běží pod námi“.

Výše zmíněné příkazy su a sudo umožňují ovlivňovat vlastníka, o skupinového vlastníka se postará newgrp. Informace o aktuálním uživateli, skupině a ostatních skupinách, ve kterých uživatel je, poskytuje příkaz id.

Výchozí práva běžných souborů jsou 666, výchozí práva adresářů 777. Při vytváření se ale aplikuje maska, která zruší v přidělovaných právech ty bity, které jsou v ní nastavené na jedničku. Maska je stejně jako vlastník a skupina vlastností procesu a stejně jako oni se dědí. U shellu ji můžeme zjistit příkazem umask bez parametrů a nastavit stejným příkazem, kterému předáme novou masku jako argument. Typické nastavení je umask 002 („nedávej w celému světu“) nebo umask 022 („w nech jen vlastníkovi“).

Úkol 4 [3b]

Jak root zařídí, aby domácí adresář uživatele hroch nebyl přístupný ostatním a aby na tom hroch nemohl nic změnit? Dodejme, že je dobrým zvykem, aby domácí adresář patřil příslušnému uživateli.

Řídicí struktury a proměnné – podruhé na návštěvě

Ve druhém dílu seriálu jsme se potkali se základními řídicími strukturami. Pokud si nepamatujete použití podmínky if nebo dvou základních cyklů níže, připomeňte si je.

if cmd; then ...; else ...; fi
while cmd; do ...; done
for i in 1 2 3 4 5; do ...; done

Dnes k těmto základům přidáme mocnější zbraně. Nejdříve si pořídíme na hraní nějký nekonečný while cyklus, tedy cyklus, jehož podmínka bude vždy splněná. Abychom nemuseli psát podmínku stylu „nula je menší než jedna“, nabízí nám shell příkazy true a false.

Ilustrace: Omráčený hroch

Ano, nepřepsali jsme se, nejsou to konstanty nabývající hodnoty pravda a nepravda jako ve většině programovacích jazyků. V shellu se jedná o samostatné příkazy, které doslova dělají nic, a to buď úspěšně, nebo neúspěšně. Podívejte se na jejich manuálové stránky.

Náš nekonečný cyklus, ve kterém si třeba budeme k proměnné X na konec připisovat D a to celé vypisovat, může vypadat takto:

X=
while true; do
	X=${X}D
	echo $X
done

Všimněte si složených závorek. Dovolíme si zde malé odbočení k proměnným. Konstrukce ${X} funguje stejně jako $X, jen ji shell i v tomto případě správně pozná. Jméno proměnné smí obsahovat písmena anglické abecedy, číslice (kromě prvního znaku) a podtržítko. Shell čte tyto znaky za dolarem, dokud se nezasekne o něco jiného, a teprve pak hledá, jestli proměnnou zná. Kdybychom jako přiřazení použili X=$XD, shell by neúspěšně hledal proměnnou XD, tedy by se do X přiřazoval prázdný řetězec.

Mohli byste namítat, že stejně dobře a elegantněji bychom mohli přidávat místo na konec na začátek pomocí X=D$X a složeným závorkám se vyhnout. Máte pravdu, tady to jde. Jenže ne vždy je možné se z průšvihu vylhat. Ještě můžeme psát X=$X""D, což je opravdu divné, a kdybychom už uvozovky používali, musíme víc přemýšlet. Za chvíli bychom ještě byli rádi za složené závorky.

Cyklus, který jsme vyrobili, je nám zatím docela nanic, protože běží donekonečna. Hodilo by se nám umět ho ve vhodnou chvíli zastavit, k tomu slouží příkaz break. Chová se podobně jako v jiných programovacích jazycích, tedy ukončí provádění celého cyklu. Pokud ho spojíme se šikovnou podmínkou, můžeme cyklus zastavit ve chvíli, kdy délka generovaného řetězce písmen D překročí deset.

X=
while true; do
	X=D$X
	delka=`echo -n $X | wc -c`
	if [ $delka -gt 10 ]; then break; fi
	echo $X
done

Všimli jste si, jak je zjišťování délky proměnné neohrabané? Ještě si musí člověk dávat pozor, jestli náhodou nepočítá i znak konce řádku… Taková běžná operace, to přeci musí jít lépe! A taky že jo: podobně jako je ${X} expandováno na obsah proměnné X, tak je ${#X} expandováno na její délku.

Společně s příkazem break jde ruku v ruce příkaz continue, který přeskočí všechny za ním následující příkazy, až před test podmínky další iterace cyklu. Vynechání řetězců kratších pěti znaků se sice dá udělat i lépe, přesto použijme příkaz continue:

X=
while true; do
	X=D$X
	delka=${#X}
	if [ $delka -lt 5 ]; then continue; fi
	if [ $delka -gt 10 ]; then break; fi
	echo $X
done

Až budete pracovat se zanořenými cykly a budete chtít příkazem break nebo continue ovlivnit jiný než nejvnitřnější z nich, vzpomeňte si, že oba příkazy mají nepovinný parametr. Zbytek už najdete. Pokud jste zvyklí na C, příkaz goto byste hledali marně.

Jako třetí mezi podobnými přichází na řadu příkaz exit, který najde použití hlavně ve skriptech. Jeho zavolání ukončí shell (je tedy silnější než break), a když se mu předá číslo (třeba exit 42), nastaví ho jako návratovou hodnotu. S ní jsme se potkali ve druhém dílu. Ve zkratce nula znamená úspěšné ukončení, cokoliv nenulového neúspěšné.

Návratová hodnota je na Linuxu 8-bitová (tedy může nabývat hodnoty 0–255), na většině jiných UNIXových systémů je však jen 7-bitová (0–127). Bash sám o sobě využívá hodnoty 126 a 127 pro své vlastní potřeby, tedy pokud chcete psát univerzální programy, omezte se jen na hodnoty 0–125.

Pokud chcete zjistit, s jakou návratovou hodnotou skončil poslední provedený příkaz, můžete k tomu využít speciální proměnnou $?. Zkuste si třeba zavolat příkazy true, false nebo (exit 42) a hned po nich echo $?. Pokud exit žádnou návratovou hodnotu nedostane jako argument, použije právě hodnotu této proměnné.

Nacyklili jsme se dost, podívejme se ještě na složitější podmínky. Poprvé potkáváme elif. Funguje analogicky ke stejnojmenné konstrukci v Pythonu, elsif v Perlu, elseif v PHP a prosté kombinaci else if v C, Javě a Pascalu. Pokud na ni při vyhodnocování dojde řada, spustí podmínku (Nezapomínejte, že podmínka je příkaz!) a kontroluje, jestli uspěla. Pokud ano, nechá spustit kód za svým then, jinak pokračuje další větví podmínky (další elif a jako poslední else).

V košatějších podmínkách se může hodit nějakou větev mít, ale nic v ní nedělat. Když hned za then napíšete středník, shell si postěžuje na syntaktickou chybu. Mohli byste použít příkaz, který nic nedělá, třeba true, ale víc se hodí funkčně shodná dvojtečka (:). Její primární účel je naznačení, že „tady nic není“, pro svou krátkost se ovšem zneužívá i na místech, kam by se víc hodilo true.

X=$(($RANDOM % 100))
if [ "$X" -lt 10 ]; then
	echo malé
elif [ "$X" -le 42 ]; then
	: nevím, něco mezi, mlčím
else
	echo velké
fi

Zde je $RANDOM speciální proměnná Bashe, která obsahuje při každé expanzi nové náhodné číslo mezi 0 a 32767. Tento úryvek kódu tedy vygeneruje náhodné číslo mezi 0 a 99 a rozhodne, jestli je malé, nebo velké. Pokud je vygenerované číslo mezi 10 a 42, neumí se rozhodnout a mlčí.

Konstrukce $((...)) je aritmetická expanze. Shell výraz uvnitř vyhodnotí a nahradí ji jeho hodnotou, přitom se k výrazu chová, jako by byl ve dvojitých uvozovkách (expanduje proměnné, vyhodnotí zpětné apostrofy a výrazy $(...)). Pokud najde jméno proměnné (bez dolaru), přečte si její hodnotu. POSIX vyžaduje, aby se v proměnných hledaly aspoň konstanty s volitelným znaménkem, Bash jde ještě dál. Pokud obsah proměnné je možné interpretovat jako výraz (třeba i jen jako jméno jiné proměnné), zkusí ho vyhodnotit. Teprve když se mu to nepovede, vyhlásí chybu.

X=21+21
Y=X
Z=Y
echo $((Z))
echo $(($Z))  # funguje, jen zbytečně složitěji

Výrazy jsou jinak přejaté z jazyka C. Podporovány jsou desítkové, oktalové a hexadecimální konstanty, většina aritmetických, bitových a logických operátorů, závorky, podmínkový operátor (dvojice ? a :) a operátory přiřazení. Bash podporuje i operátory ++ a --. Vyhodnocuje se ve znaménkovém celočíselném typu – norma vyžaduje signed long nebo větší. Proběhlá přiřazení do proměnných shell uvidí i po dokončení expanze.

Alternativou k shellové aritmetice jsou příkaz expr, který podle nás nemá žádné výhody, a příkaz bc, který má vlastní jazyk a neomezenou přesnost.

Funkce

V shellu už umíme kdeco z toho, co umí běžné programovací jazyky. Proměnné, příkazy, řídicí konstrukce, aritmetiku, vstup a výstup, … O jednom důležitém konceptu z programovacích jazyků ale dosud nepadlo slovo. O funkcích.

V běžných jazycích jsou funkce posloupností příkazů, která má nějaké (formální) parametry. Při volání se funkci předají argumenty (skutečné parametry), které se dosadí do formálních parametrů, a příkazy se spustí. V shellu je situace úplně stejná.

Mnoho ze znalostí o skriptech, které jste si přinesli z předchozího dílu, platí i pro funkce. Funkce také dostává poziční parametry, také má proměnné $#, $@, $1 (až $9), má návratovou hodnotu a volání funkce i skriptu vypadají jako každý jiný příkaz (žádné závorky kolem argumentů!).

Na rozdíl od skriptu se pro funkci nespouští zvláštní shell, její příkazy běží ve stejném procesu, který ji volá. Má tedy stejný obsah proměnné $0, může využívat (a trvale měnit!) i neexportované proměnné a při zavolání exit neskončí jen ona sama, ale i celý shell. Pro nastavení návratové hodnoty používá příkaz return.

Funkce se tedy chová stejně jako skript vložený příkazem . (bashovsky také source), jen se jinak definuje a nemusí být ve vlastním souboru.

Ukažme si pro ilustraci definici funkce a její volání.

# definice
echo_t() {
	test -t 1 || echo "$@" > /dev/tty
	echo "$@"
}
# volání
echo_t hroch > soubor

Tato funkce se jmenuje echo_t, všechny své argumenty vytiskne na svůj standardní výstup, a pokud standardní výstup nejde na terminál (je přesměrovaný a ne zrovna do terminálu), argumenty vytiskne i přímo na terminál.

Definice funkce začíná jejím jménem, následovaným kulatými závorkami. Pro jméno funkce platí stejná pravidla jako pro jméno proměnné. Jmenné prostory funkcí a proměnných jsou oddělené – můžeme mít stejně pojmenovanou funkci i proměnnou, obě budou fungovat správně. Před jménem funkce Bash dovoluje ještě (zbytečné) klíčové slovo function.

Za jménem a kulatými závorkami následuje téměř obyčejný složený příkaz, jak ho znáte z minula. Zvláštní je v tom, že místo aby se v něm hned expandovaly proměnné a vůbec dělo všechno, co shell se svým vstupem provádí, shell si ho zapamatuje beze změny a expanze provádí až při volání.

Za zmínku stojí, že za složeným příkazem mohou být přesměrování vstupů a výstupů, která se stanou součástí definice funkce; snadno tedy můžete napsat třeba hloupou funkci pro záznam zpráv opatřených časem pořízení záznamu:

log() {
	date
	echo "$@"
	echo
} >> zaznamy_chyb

Co kdybychom ale chtěli každý argument zalogovat jako samostatný záznam a na každý záznam mít jeden řádek? Mohli bychom použít cyklus, který projde jednotlivé argumenty. Proč bychom se ale nudili už dobře známým, když si můžeme ukázat pěknou novou utilitu?

Úkol 5 [5b]

Napište funkci, která dostane jako svůj parametr jméno souboru se zdrojovým kódem, načte a předzpracuje jej pro kompilátor a výsledek vypíše na standardní výstup.

Zdrojový kód obsahuje jednotlivé textové tokeny oddělené právě jednou mezerou. Komentáře jsou uvozené tokenem COMMENT a platí do konce řádku. Program je ukončený buď koncem souboru, nebo tokenem BYE.

Úkolem funkce je opsat zdrojový kód až do konce programu s vynecháním komentářů. Na výstupu by se neměly objevit ani žádné prázdné řádky.

Poznámka: Na tuto úlohu nepoužívejte příkazy grep, sed ani žádný jiný jazyk (AWK, Perl, …).

Ilustrace: Formální hroch

Formátovaný výstup

Už jsme vám až příliš dlouho zamlčovali printf. Mnoho jazyků má funkci toho jména a ve všech má velmi podobný význam parametrů a stejný účel – pěkné a snadné formátování výstupu. Na rozdíl od echo implicitně neodřádkovává, takže často budete na konec jejího prvního argumentu muset psát \n. To je další rozdíl proti běžnému echo, umí céčkové escape-sekvence jako \n pro konec řádku nebo \t pro tabulátor (echo s přepínačem -e nebo POSIXové echo escape-sekvence také vyhodnocují).

První argument printf je šablona, do které dosazuje zbylé argumenty. Pokud je argumentů málo, domyslí si pár prázdných navíc. Pokud jich je moc, zopakuje šablonu. Toho využívá i výše slíbená vylepšená funkce log:

log() {
	d="$(date)"   # znak % neobsahuje
	printf "[$d] %s\n" "$@"
} >> zaznamy_chyb

V šabloně jsou formátovací direktivy, které poznáte podle znaku % na začátku. (Pokud procento potřebujete vypsat doslova, pište %%.) Za každou direktivu dosadí printf pozicí odpovídající argument. Běžně budete potřebovat %s pro string a %d pro desítkový zápis čísla. Mimo jiné printf umí i převádět čísla do šestnáctkové (%x) a osmičkové soustavy (%o). Direktivy mají kromě povinného typu (s pro string, …) ještě dost nepovinných částí. Na jejich samostudium se dlouhé zimní večery náramně hodí. ;-)

Úkol 6 [2b]

Napište skript, který formátuje /etc/passwd do podoby tabulky.

Závěr

Tento díl byl delší než oba předchozí dohromady. Dotáhli jsme v něm ale do konce spoustu rozdělaných věcí a poskytli vám tak mnohem ucelenější pohled na UNIXový svět. Pokud bychom měli zrekapitulovat, co si z tohoto dílu máte odnést, mohl by takový seznam vypadat následovně:

V příštím díle se už konečně podíváme na slíbené utility pro práci s textem. Přesným obsahem se nechte příjemně překvapit. :-)

Tomáš „Palec“ Maleček

Řešení