Cvičení: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14.

Na tomto cvičení se podíváme jak zjednodušit sestavování složitého software. A jak efektivně hledat (a nahrazovat) v textových datech.

Tohle cvičení také obsahuje malý domácí úkol za dva body.

Čtení síťové konfigurace

Než se ponoříme do hlavního tématu, uděláme malou odbočku k jedné praktické věci, která se nám bude velmi hodit. A to jak zobrazit síťovou konfiguraci vašeho počítače z příkazového řádku.

Už jsme viděli nmcli, ale existují i další nástroje. Mezi nimi také ip (z balíčku iproute2), který může být také využíván k nastavení sítě (ale spíše na serverech než na pracovních stanicích, kde je obvyklejší NetworkManager).

V následujícím textu budeme předpokládat, že váš počítač je připojen k internetu (to se týká i virtualizované instalace systému Linux).

Základním příkazem pro nastavování a zobrazování konfigurace sítě je ip.

Prozatím je pro nás asi nejužitečnější ip addr.

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s31f6: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 1000
    link/ether 54:e1:ad:9f:db:36 brd ff:ff:ff:ff:ff:ff
3: wlp58s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 44:03:2c:7f:0f:76 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.105/24 brd 192.168.0.255 scope global dynamic noprefixroute wlp58s0
       valid_lft 6209sec preferred_lft 6209sec
    inet6 fe80::9ba5:fc4b:96e1:f281/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
8: vboxnet0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 0a:00:27:00:00:00 brd ff:ff:ff:ff:ff:ff

Příkaz zobrazil seznam čtyř rozhraní (lo, enp0s31f6, wlp58s0 a vboxnet0), která jsou v počítači k dispozici. Váš seznam se bude lišit, stejně tak se mohou lišit jména rozhraní.

Název rozhraní naznačuje jeho typ.

  • lo je zařízení zpětné smyčky (loopback), a bude vždy přítomno. Pomocí něj můžete testovat síťové aplikace i bez “skutečného” připojení.
  • enp0s31f6 (často také eth*) je kabelový ethernet.
  • wlp58s0 je bezdrátový adaptér (wi-fi).
  • vboxnet0 je virtuální síťová karta, kterou VirtualBox používá při vytváření virtuální podsítě pro vaše virtuální počítače (pravděpodobně ji tam mít nebudete).

Pokud jste připojeni přes VPN, můžete vidět i rozhraní tun0.

Stav rozhraní (spuštěno – UP – nebo ne) je na stejném řádku jako název adaptéru.

Řádek začínající link/ obsahuje MAC adresu adaptéru. Řádky s inet udávají IP adresu přidělenou tomuto rozhraní včetně specifikace rozsahu sítě. V tomto příkladu má lo adresu 127.0.0.1/8 (samozřejmě), enp0s31f6 je bez adresy (state DOWN) a wlp58s0 má adresu 192.168.0.105/24 (tj. 192.168.0.105 se síťovou maskou 255.255.255.0).

Vaše adresy se budou mírně lišit, ale obvykle se také zobrazí privátní adresa (za NATem), protože se pravděpodobně připojujete přes směrovač (router) k vašemu poskytovateli internetu.

Regulární výrazy (neboli regex – regular expression)

Už jsme zmínili, že systémy Unixové rodiny jsou založeny na textových souborech. Utility, které jsme zatím viděli, nám umožnili základní operace s nimi, ale žádné z nich nebyly opravdu mocné. Použitím regulárních výrazů to změníme.

Nebudeme zde řešit teoretické detaily – na to je předmět Automaty a gramatiky. My se na regulární výrazy budeme dívat jako na jednoduché nástroje pro hledání vzorů (patternů) v textu.

Například nás můžou zajímat:

  • řádky začínající datem a obsahující HTTP kód 404,
  • řádky obsahující náš login,
  • nebo řádky předcházející řádkům s platnými názvy souborů.
Přestože regulární výrazy jsou velmi silné, jejich použití se trochu komplikuje nepříjemným faktem, že různé nástroje používají trochu odlišnou syntaxi. Mějte to na paměti například při používání příkazů grep a sed. Knihovny pro práci s regulárními výrazy jsou taky k dispozici ve většině programovacích jazyků, ale opět pozor na rozdíly v jejich syntaxi.

Nejzákladnějším nástrojem na vyhledávání regulárními výrazy v souborech je grep. Spustíme-li grep regex soubor, vypíší se všechny řádky souboru, které odpovídají zadanému regulárnímu výrazu (s -F je výraz interpretován jako obyčejný řetězec, nikoliv jako regulární výraz).

Legenda říká, že g ve jméně znamená “globally”, re je regex a p print.

Syntaxe regexů

V tom nejjednodušším tvaru regex hledá zadaný řetězec (obvykle s ohledem na velikost písmen).

system

Tímto zachytíme všechny podřetězce system v textu. Při použití grepu to znamená, že všechny řádky obsahující system (kdekoliv) se vypíší.

Chceme-li hledat řádky začínající tímto slovem, musíme přidat ukotvení ^.

^system

Má-li řádek končit zadaným vzorem, potřebujeme použít ukotvení $. V shellu je bezpečnější kolem vzoru používat jednoduché uvozovky, abychom se vyvarovali expanzi proměnných, atp.

system$

Také můžeme najít všechny řádky začínající jedním z písmen r, s, nebo t pomocí seznamu [...].

^[rst]

Vypadá to trochu jako wildcard, ale regexy jsou mnohem silnější a syntaxe se trochu liší.

Pro vlastní hledání musíme výraz samozřejmě předat programu grep nějak takto (tady hledáme v /etc/passwd):

grep '^[rst]' /etc/passwd

Zkusme najít všechna třímístná čísla:

[0-9][0-9][0-9]
Tomuto budou vyhovovat jak všechna třímístná čísla, tak třeba i čtyřmístná: regulární výrazy bez ukotvení se vůbec nestarají o okolní znaky.

Můžeme také hledat řádky nezačínající žádným ze znaků mezi r a z. (První ^ je ukotvení, zatímco druhá neguje množinu v [].)

^[^r-z]

Kvantifikátor * značí, že předchozí část regexu se může vyskytovat vícekrát nebo třeba vůbec. Např. tímto najdeme všechny řádky, které obsahují jen číslice (ale zachytíme tím i prázdné řádky!):

^[0-9]*$

Poznamenejme, že to nevyžaduje, aby všechny číslice byly stejné.

Tečka . odpovídá libovolnému jednomu znaku (kromě zalomení řádku). Následující regex pak zachytí řádky začínající super a končící ious:

^super.*ious$

Abychom mohli aplikovat * na složitější podvýraz, můžeme jej obalit (...). Následující regex zachytí bana, banana, bananana, atd.:

ba(na)*na

Použijeme-li + místo *, je vyžadován aspoň jeden výskyt. Tímto tak zachytíme všechna dekadická čísla:

[0-9]+

Svislá čára (|, příp. pipa) může oddělovat alternativy. Např. můžeme zachytit řádky složené z Meow a Quork:

^(Meow|Quork)*$

Konstrukce [abc] je tak jen zkratkou pro (a|b|c).

Další užitečnou zkratkou je kvantifikátor {N}: ten říká, že předchozí regex se musí opakovat N krát. Můžeme také použít {N,M} pro rozsah. Např. můžeme zachytit řádky obsahující 4–10 malých písmen uzavřených v uvozovkách:

^"[a-z]{4,10}"$

Nakonec je tu zpětné lomítko, které mění to, jestli je následující znak považovaný za speciální. \. zachytí opravdu tečku a \* hvězdičku. Naproti tomu mnoho dialektů regexů (vč. grepu bez dalších přepínačů) vyžaduje +, (, | a { odescapované, aby byly rozpoznány jako regexové operátory. (Můžete spustit grep -E nebo egrep pro aktivování rozšířených regulárních výrazů, které všechny speciální znaky rozpoznávají jako operátory bez zpětných lomítek.)

grep skončí s nulovým exit kódem pouze pokud našel alespoň jeden odpovídající řádek.

Takže může být využit takto:

if ! echo "$input" | grep 'regex'; then
    echo "Input is not in correct format." >&2
    ...
fi

Ačkoliv regulární výrazy i shellové zástupné znaky (wildcards) si jsou trochu podobné, jde o úplně odlišná zvířátka. Regulární výrazy jsou mnohem silnějším nástrojem a také jsou patřičně složitější.

Shell používá jen wildcardy (pokud nepoužíváte některá z rozšíření Bashe).

Nahrazování v textu

Plná síla regulárních výrazů se ukáže, když je použijeme na nahrazování vzorů. Ukážeme si to na příkazu sed (stream editor), který umí provádět textové transformace založené na regulárních výrazech.

sed a grep používají mírně odlišnou syntaxi regexu. Pokud si nejste jisti, vždy se podívejte do manuálové stránky. Největší rozdíly mezi nástroji a jazyky jsou obecně v zacházení se speciálními znaky pro opakování nebo seskupování ((), {}).

V nejjednodušší podobě, sed nahrazuje jedno slovo jiným. Příkaz přečte nahrazení (s, substitute), pak jednoznakový oddělovač následovaný textem k nahrazení (levá strana substituce), opět stejný oddělovač, pak náhrada (pravá strana) a nakonec opět oddělovač. (Oddělovač je obvykle :, /, nebo #, ale obecně to může být libovolný znak, který se nepoužívá ve zbytku příkazu bez escapování.)

sed 's:magna:angam:' lorem.txt

Poznamenejme, že tímto nahradíme vždy jen první výskyt na každém řádku. Přidání modifikátoru g (global) na konec příkazu způsobí nahrazení všech výskytů:

sed 's:magna:angam:g' lorem.txt

Text, který má být nahrazen, může být libovolný regulární výraz, např.:

sed 's:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]:DATE-REDACTED-OUT:g' lorem.txt

Pravá strana se může odkazovat na text zachycený levou stranou. Můžeme použít & pro celou levou stranu nebo \n pro n-tou skupinu (...) na levé straně.

Následující příklad převede datum do českého tvaru (DD. MM. RRRR). Podobně jako u grepu musíme odescapovat znaky ( a ), aby fungovaly jako seskupující operátory namísto literálů ( a ).

sed 's:\([0-9][0-9][0-9][0-9]\)-\([0-9][0-9]\)-\([0-9][0-9]\):\3. \2. \1:g'

Průběžný příklad pro zbytek cvičení

Opět se vrátíme k našemu příkladu generování webových stránek a použijeme jej jako průběžný příklad pro zbytek tohoto cvičení.

Opět použijeme jednodušší verzi, která vypadala takto:

#!/bin/bash

set -ueo pipefail

pandoc --template template.html index.md >index.html
pandoc --template template.html rules.md >rules.html
./table.py <score.csv | pandoc --template template.html --metadata title="Score" - >score.html

Všimněte si, že pro index a rules máme Markdown soubory, ze kterých vytvoříme HTML. Stránka score je generována z CSV souboru.

Příprava

Prosím, vytvořte fork repozitáře s webem, abyste si mohli příklady sami vyzkoušet (tento repozitář budeme ještě používat na jednom z dalších cvičení, takže ho zatím neodstraňujte).

Motivace pro používání systémů sestavení

V našem příkladu je celý web sestaven v několika krocích, kdy jsou stránky HTML generovány z různých zdrojů. To je vlastně velmi podobné tomu, jak se software sestavuje ze zdrojových kódů (uvažujme zdrojové kódy v jazyce C, které se kompilují a linkují dohromady).

Zatímco výše uvedené kroky nevytvoří spustitelný soubor (což je typický případ pro vývoj softwaru), reprezentují obvyklý scénář.

Sestavení softwaru se obvykle skládá z mnoha kroků, které zahrnují tak odlišné činnosti jako třeba:

  • kompilace zdrojáků do nějakého meziformátu
  • linkování výsledné binárky
  • vytvoření bitmapové grafiky v různém rozlišení z jediného vektorového obrázku
  • generování dokumentace ze zdrojového kódu
  • příprava souborů pro lokalizaci (překlad)
  • vytvoření samorozbalujícího archívu
  • nahrání softwaru na webový server
  • zveřejnění artefaktu v repozitáři balíčků

Skoro všechny jsou vlastně hrozně jednoduché. Složité je poskládat je dohromady. To jest, jak je spustit ve správném pořadí a jak spustit jednotlivé programy se správnými parametry (volbami).

Například, než může být vytvořen instalátor, musí být nachystané všechny ostatní soubory. Soubory pro překlad obvykle závisí na předzpracování nějakých zdrojových souborů, ale musí být nachystány než je vytvořena výsledná binárka. Atd.

I pro malé projekty může být množství kroků poměrně velké. Ale jsou – svým způsobem – nezajímavé: nechcete si je pamatovat. Chcete ten software sestavit!

Všimněte si, že vám s tím vším často pomůže vaše IDE — jediným kliknutím. Ne každý však používá stejné IDE a možná dokonce grafické rozhraní vůbec nemáte.

Kromě toho chcete obvykle spustit sestavení jako součást každé revize — typickým příkladem jsou pipeline GitLabu, které používáme pro testy: spouštějí se bez grafického rozhraní, přesto chceme software sestavit (a také otestovat). Kodifikace tohoto postupu ve skriptu sestavení to zjednodušuje prakticky pro každého.

Náš výše zmíněný skript build.sh je vlastně docela pěkný. Je snadno pochopitelný, neobsahuje žádnou složitou logiku a nový člen týmu nemusí zkoumat všechny drobné detaily a může prostě spustit jediný skript build.sh.

Skript je v pohodě, ale zbytečně přepisuje soubory i když nedošlo ke změně. V našem malém příkladu to nevadí (konec konců, počítač máme dost rychlý).

Ale ve větším projektu, kde bychom pracovali s tisícovkami souborů (klidně se podívejte na zdrojáky Linuxového jádra, Firefoxu nebo LibreOffice) je to velký rozdíl. Pokud se vstupní soubor nezměnil (třeba jsme změnili jen rules.md) nemusíme přetvářet ostatní soubory (nemusíme znovu vytvářet index.html).

Trochu teď skript rozšíříme.

...

should_generate() {
    local barename="$1"
    # File does not exist ... we should generate it
    if ! [ -e "${barename}.html" ]; then
        return 0
    fi
    # Markdown is newer than HTMLM ... we should regenerate it
    if [ "${barename}.md" -nt "${barename}.html" ]; then
        return 0
    else
        return 1
    fi
}

...

should_generate index && pandoc --template template.html index.md >index.html
should_generate rules && pandoc --template template.html rules.md >rules.html

...

Tohle můžeme udělat pro každý příkaz a zrychlíme tím vytváření webu.

Ale.

To je dost práce. A pravděpodobně by ušetřený čas by promrhán přepisováním našeho skriptu. Nemluvě o tom, že výsledek je děsivý. A dost náročný na údržbu.

Často také potřebujeme sestavit jen část projektu: např. jen vygenerovat dokumentaci (bez dalšího zveřejnění, třeba). Ačkoliv rozšíření skriptu tímto způsobem je možné, pro velké projekty to nebude fungovat dobře.

if [ -z "${1:-}" ]; then
    ... # build here
elif [ "${1:-}" = "clean" ]; then
    rm -f index.html rules.html score.html
elif [ "${1:-}" = "publish" ]; then
    cp index.html rules.html score.html /var/www/web-page/
else
    ...

Naštěstí je tu lepší cesta.

Existují speciální nástroje zvané build systémy (česky možná také nástroje pro řízení překladu/sestavení) které mají jediný účel: koordinovat proces sestavení. Uživateli dávají vysokoúrovňový jazyk, ve kterém může zachytit výše zmíněné kroky pro sestavení softwaru.

V tomto cvičení se zaměříme na make. make je poměrně starý systém, ale pořád je hojně používán. Je to také jeden z nejjednodušších nástrojů z této kategorie: hodně toho musíte připravit ručně, ale pro vysvětlení principů je to perfektní. Budeme mít plnou moc nad celým procesem a uvidíte, co se všechno děje.

make

Nejprve se přesuňte do kořenového adresáře (lokálního klonu vašeho forku) repozitáře příkladu s webem, prosím.

Soubory v tomto adresáři jsou prakticky stejné jako v našem shellovém skriptu výše, ale je zde jeden soubor navíc: Makefile. Všimněte si, že Makefile je napsán s velkým M, aby byl snadno rozlišitelný (ls v nelokalizovaném nastavení řadí velká písmena jako první).

Tento soubor je řídicím souborem pro sestavovací systém make, který dělá přesně to, co jsme se snažili napodobit v předchozím příkladu. Obsahuje posloupnost pravidel pro sestavování souborů.

K přesné syntaxi pravidel se dostaneme brzy, ale nejprve si s nimi pojďme pohrát. Spusťte následující příkaz:

make

Zobrazí se následující výstup (pokud jste už některé příkazy provedli ručně, může se výstup lišit):

pandoc --template template.html index.md >index.html
pandoc --template template.html rules.md >rules.html

make vypíše příkazy, které provede, a spustí je. Vytvořil pro nás webové stránky: všimněte si, že byly vygenerovány soubory HTML.

Soubor version.inc.html prozatím vůbec negenerujeme.

Spusťte znovu make.

make: Nothing to be done for 'all'.

Jak vidíte, make byl dost chytrý na to, aby rozpoznal, že vzhledem k tomu, že se žádný soubor nezměnil, není třeba nic spouštět.

Aktualizujte soubor index.md (funguje i touch index.md) a znovu spusťte make. Všimněte si, že soubor index.html byl znovu sestaven, zatímco soubor rules.html zůstal nedotčen.

pandoc --template template.html index.md >index.html

Tomuto postupu se říká inkrementální sestavení (sestavujeme pouze to, co bylo potřeba, místo abychom sestavovali vše od začátku).

Jak jsme uvedli výše: v našem malém příkladu to není příliš zajímavé. Jakmile však budou vstupních souborů tisíce, rozdíl bude obrovský.

Je také možné spustit make index.html a požádat o přestavbu pouze index.html. Sestavení je opět inkrementální.

Pokud si přejete vynutit sestavení, spusťte make s -B. Často se tomu říká nepodmíněné sestavení.

Jinými slovy, make nám umožňuje zachytit jednotlivé jednoduché příkazy potřebné pro sestavení projektu (bez ohledu na to, zda kompilujeme a linkujeme programy v jazyce C nebo generujeme webovou stránku) do uceleného skriptu.

Přestavuje pouze věci, které je třeba přestavět, a co je zajímavější, bere v potaz závislosti. Například pokud se scores.html generuje z scores.md, který se sestavuje z scores.csv, stačí zadat, jak se má sestavit scores.md z scores.csv a jak se má vytvořit scores.html z scores.md, a make zajistí správné pořadí.

vysvětlení formátu Makefile

Makefile je řídicí soubor pro systém sestavování s názvem make. V podstatě se jedná o jazyk specifický pro danou doménu (domain-specific language), který zjednodušuje nastavení skriptu bez nutnosti funkce should_generate, kterou jsme zmínili výše.

Na rozdíl od většiny programovacích jazyků make rozlišuje tabulátory a mezery. Veškeré odsazení v souboru Makefile musí být provedeno pomocí tabulátorů. Musíte se ujistit, že váš editor nenahrazuje tabulátory na mezery. To je také častý problém při kopírování kusů kódu z webového prohlížeče.

(Obvykle váš editor rozpozná, že Makefile je speciální název souboru, a sám se přepne na politiku pouze tabulátory.) Pokud místo toho použijete mezery, obvykle se zobrazí chyba typu Makefile:LINE_NUMBER: *** missing separator. Stop..

Soubor Makefile obsahuje posloupnost pravidel. Pravidlo vypadá takto:

index.html: index.md template.html
    pandoc --template template.html index.md >index.html

Název před dvojtečkou je cíl pravidla. Obvykle je to název souboru, který chceme vytvořit. Zde je to index.html.

Zbytek prvního řádku je seznam závislostí – souborů, ze kterých je cíl sestaven. V našem příkladu jsou závislostmi soubory index.md a template.html. Jinými slovy: když se tyto soubory (index.md a template.html) změní, musíme znovu sestavit index.html.

Třetí částí jsou následující řádky, které musí být odsazeny tabulátorem. Obsahují příkazy, které je třeba provést, aby byl cíl sestaven. Zde je to volání příkazu pandoc.

make spustí příkazy, pokud je cíl zastaralý. To znamená, že buď chybí cílový soubor, nebo je jedna či více závislostí novější než cíl.

Zbytek souboru Makefile je podobný. Jsou zde pravidla pro další soubory a také několik speciálních pravidel.

Speciální pravidla

Speciální pravidla jsou all, clean a .PHONY. Neurčují soubory, které mají být sestaveny, ale spíše speciální akce.

all je tradiční název pro první pravidlo v souboru. Říká se mu výchozí pravidlo a je použito, pokud spustíte make bez argumentů. Obvykle nemá žádné příkazy a závisí na všech souborech, které by měly být sestaveny jako výchozí.

clean je speciální pravidlo, které obsahuje pouze příkazy, ale žádné závislosti. Jeho účelem je odstranit všechny vygenerované soubory, pokud chcete vyčistit svůj pracovní prostor. Obvykle clean odstraní všechny soubory, které nejsou verzovány (tj. nejsou pod kontrolou systému Git).

To lze považovat za nesprávné použití programu make, které má však dlouhou tradici. Z pohledu make jsou cíle all a clean stále považovány za názvy souborů. Pokud vytvoříte soubor s názvem clean, speciální pravidlo přestane fungovat, protože cíl bude považován za aktuální (existuje a žádná závislost není novější).

Chcete-li se této pasti vyhnout, měli byste výslovně říci make, že cílem není soubor. To se provede tak, že jej uvedete jako závislost speciálního cíle .PHONY (všimněte si úvodní tečky).

Obecně je vidět, že make má spoustu zvláštností. Často je to tak u programů, které začaly jako jednoduchý nástroj a prošly si 40 lety postupného vývoje, kdy pomalu přibývaly funkce. Přesto je to jeden z nejpoužívanějších build systémů. Často také slouží jako back-end pro pokročilejší nástroje — ty vygenerují Makefile z přívětivější specifikace a delegují práci na make.

Cvičení

Rozšiřte soubor Makefile o volání generovacího skriptu pro stránku score.html. Nezapomeňte aktualizovat pravidla all a clean.

Řešení.

Podadresář out/ je prázdný (obsahuje pouze soubor .gitignore, který určuje, že všechny soubory v tomto adresáři budou systémem Git ignorovány a nebudou tedy zobrazeny v git status).

Aktualizujte Makefile pro generování souborů do tohoto adresáře. Důvody jsou zřejmé:

  • Vygenerované soubory nezaneřádí váš pracovní adresář (stejně z nich nechcete dělat commit).
  • Při synchronizaci s webovým serverem můžeme zadat celý adresář, který má být zkopírován (místo zadávání jednotlivých souborů).

Řešení.

Přidejte speciální (.PHONY) cíl upload, který zkopíruje vygenerované soubory na počítač v Rotundě. Vytvořte si tam (ručně) adresář ~/WWW. Jeho obsah bude dostupný jako http://www.ms.mff.cuni.cz/~LOGIN/.

Poznamenejme, že je třeba přidat potřebná oprávnění souborového systému AFS pomocí příkazu fs setacl (vizte cvičení 09).

Řešení.

Přidejte generování PDF z rules.md (pomocí LibreOffice). Všimněte si, že soffice podporuje parametr --outdir.

Zamyslete se nad následujícím:

  • Kam umístit mezisoubor ODT?
  • Má být pro generování souboru ODT vytvořeno zvláštní pravidlo, nebo má být provedeno jediným pravidlem se dvěma příkazy?

Řešení.

Zlepšení udržovatelnosti souboru Makefile

Soubor Makefile začíná obsahovat příliš mnoho opakujícího se kódu.

Ale i s tím vám může make pomoci.

Odstraňme všechna pravidla pro generování out/*.html z *.md a nahraďme jej tímto pravidlem:

out/%.html: %.md template.html
      pandoc --template template.html -o $@ $<

To je vzorové pravidlo (pattern rule), které zachycuje myšlenku, že HTML je generováno z Markdown. Znak procenta ve specifikaci závislostí a cíle představuje tzv. stem — proměnlivou (tj. měnící se) část vzoru.

V příkazové části používáme proměnné make. Proměnné make začínají na dolar jako v shellu, ale nejsou stejné.

$@ je skutečný cíl a $< je první závislost.

Spusťte make clean && make a ověřte, zda se web generuje i při použití vzorových pravidel.

Kromě vzorových pravidel rozumí make také (uživatelským) proměnným. Ty mohou zlepšit čitelnost, protože můžete oddělit konfiguraci od příkazů. Například:

PAGES = \
      out/index.html \
      out/rules.html \
      out/score.html

all: $(PAGES) ...
...

Všimněte si, že na rozdíl od shellu jsou proměnné expandovány pomocí $(VAR) konstrukcí. (S výjimkou speciálních proměnných, jako je $<.)

Nepřenositelná rozšíření

make je velmi starý nástroj, který existuje v mnoha různých implementacích. Dosud zmíněné funkce by měly fungovat s jakoukoli verzí make. (Přinejmenším s přiměřeně nedávnou verzí. Staré verze make neměly .PHONY ani vzorová pravidla.)

Poslední doplněk bude fungovat pouze v GNU make (ale ten je v Linuxu výchozí, takže by s ním neměl být žádný problém).

Soubor Makefile změníme takto:

PAGES = \
      index \
      rules \
      score

PAGES_TMP=$(addsuffix .html, $(PAGES))
PAGES_HTML=$(addprefix out/, $(PAGES_TMP))

Uchováváme pouze základní název každé stránky a počítáme výstupní cestu. $(addsuffix ...) a $(addprefix ...) jsou volání vestavěných funkcí. Formálně jsou všechny argumenty funkcí řetězce, ale v tomto případě jsou jména oddělená čárkami považována za seznam.

Všimněte si, že jsme přidali PAGES_TMP pouze pro zlepšení čitelnosti při prvním použití této vlastnosti. Za normálních okolností byste do PAGES_HTML přiřazovali přímo toto.

PAGES_HTML=$(addprefix out/, $(addsuffix .html, $(PAGES)))

To bude ještě užitečnější, až budeme chtít pro každou stránku vygenerovat také soubor PDF. Můžeme přidat vzorové pravidlo a vytvořit seznam PDF pomocí $(addsuffix .pdf, $(PAGES)).

Úlohy k ověření vašich znalostí

Očekáváme, že následující úlohy vyřešíte ještě před příchodem na cvičení, takže se budeme moci o vašich řešeních na cvičení pobavit.

Hodnocený malý domácí úkol

Poznámka: možná dává větší smysl začít s jednoduššími příklady níže a teprve pak se vrátit k tomuto úkolu.

Vaším úkolem je napsat filter pro předzpracování zjednodušeného TAP výstupu. Doporučujeme použít Python, ale budeme akceptovat i řešení v Bashi (se sedem ale nikoliv s AWK či PERLem).

Takto bude vypadat vstup:

1..6
ok 1 One - Smoke test (ok=1)
ok 2 One - Works with 0 (ok=1,fail=-2)
ok 3 Two - Smoke test (ok=1)
not ok 4 Two - Empty file (fail=-1)
ok 5 Two - One-byte long file (fail=-2)
not ok 6 Two - Non-existent file (ok=3)

Filter jej převede do následujícího formátu.

passed:1:One:Smoke test
passed:1:One:Works with 0
passed:1:Two:Smoke test
failed:-1:Two:Empty file
passed:0:Two:One-byte long file
failed:0:Two:Non-existent file

Jak je patrno, proběhly následující transformace:

  • Prefix ok a not ok jsou převedeny na passed či failed.
  • Číslo testu už nepotřebujeme (a stejně tak ani první řádku).
  • Název testu byl rozdělen okolo pomlčky (-) do dvou sloupečků (tj. název sady (suite) a vlastního testu).
  • Informace v závorkách byla použita pro spočítání bodů (skóre) do druhého sloupečku.
    • Body mohou být uvedeny jak pro stav passed tak i failed (a bereme =0, když informace není uvedena).
    • ok=N znamená, že když test prošel, přidáme N bodů.
    • fail=M znamená, že když test selhal, přidělíme M bodů (M bude ale obvykle záporné).

Můžete předpokládat, že vstup je více méně v pořádku: nerozpoznané (rozbité) řádky budou potichu přeskočeny. Jinak se držet formátu uvedeného výše. Pochopitelně, názvy sad a testů se budou lišit, stejně jako přiřazení bodů, ale žádné další informace (jako je tu našich testů v CI) nebo přeskočené testy nebudou.

Počítáme, že použijete regulární výrazy v Pythonu společně s funkcí .split() na řetězcích.

Ve skutečnosti používáme ten samý formát pro některé naši testy nad domácími úlohami nebo písemkami. Používáme knihovnu pro zpracování TAP výstupu (prosím, v této úloze ji nepoužívejte), abychom měli i další informace, ale získání bodů probíhá skrz regulární výraz.

A jen tak mimochodem, pokud skript spustíme jako ./10/tapscore < input.tap | cut -d : -f 2 | paste -sd+ | bc tak budeme mít hezký součet všech bodů.

Termín dokončení tohoto úkolu je 4. května.

Prosím, řešení odevzdejte jako 10/tapscore do vašeho GitLabového repozitáře student-LOGIN. Všimněte si, že soubor nemá příponu, protože je možné odevzdat řešení jak v Pythonu, tak v shellu.

Tuto úlohu vám může zkontrolovat GitLab skrz automatizované testy.

Najděte všechny řádky v /etc/passwd, které obsahují číslici 9.

Účty se /sbin/nologin v /etc/passwd jsou obvykle systémové účty, které nepoužívá žádný uživatel. Vypište seznam těchto účtů.

Řešení.

Vyhledejte všechny řádky v /etc/passwd, které začínají některým z písmen A, B, C nebo D (nerozlišujte velká a malá písmena).

Řešení.

Najděte všechny řádky, které obsahují sudý počet znaků.

Řešení.

Najděte všechny e-mailové adresy. Předpokládejte, že platná e-mailová adresa má formát <s1>@<s2>.<s3>, kde každá sekvence <sN> je neprázdný řetězec znaků anglické abecedy a sekvence <s1> a <s2> mohou obsahovat také číslice nebo tečku ..

Řešení.

Vypište všechny řádky obsahující slovo (v anglické abecedě), které začíná velkým písmenem a všechna ostatní písmena jsou malá. Otestujte, že slovo TeX nebude zachyceno.

Řešení.

Odstraňte všechny koncové mezery a tabulátory.

Řešení.

Každé slovo (neprázdnou posloupnost znaků anglické abecedy) vložte do závorek.

Řešení.

Nahraďte “Name Surname” za “Surname, N.”.

Řešení.

Odstraňte všechny prázdné řádky. Nápověda.

Řešení.

Přeformátujte vstup tak, aby každá věta byla na samostatném řádku. Předpokládejte, že každá věta začíná velkým anglickým písmenem a končí znakem ., ! nebo ?; mezi větami může být libovolný počet mezer.Nápověda.

Řešení.

Napište filtr pro výstup příkazu ip addr, který vypíše název zařízení následovaný jeho adresou IPv4 a délkou síťového prefixu.

Pro rozhraní, které nemá přidělenou adresu IPv4, vypište místo ní speciální adresu 0.0.0.0/0.

Příklad z cvičení 08 by byl zpracován do následujícího výstupu:

lo 127.0.0.1/8
enp0s31f6 0.0.0.0/0
wlp58s0 192.168.0.105/24
vboxnet0 0.0.0.0/0

Případ chybějící IP adresy váš skript pravděpodobně značně zkomplikuje. Začněte s variantou, která předpokládá, že každé rozhraní má adresu přiřazenou.

Tento příklad vám může zkontrolovat GitLab skrz automatizované testy. Uložte vaše řešení jako 10/netcfg.sh a commitněte ho (pushněte) do GitLabu.

Napište skript, který znormalizuje danou cestu.

Skript očekává jediný argument: cestu, kterou má normalizovat. Můžete předpokládat, že argument bude vždy uveden.

Skript provede normalizaci cesty následujícím způsobem:

  • odkazy na aktuální adresář budou odstraněny, protože jsou nadbytečné
  • odkazy na nadřazený (rodičovský) adresář budou odstraněny takovým způsobem, aby se nezměnil význam cesty (možná i opakovaně)
  • skript nebude převádět relativní cestu na absolutní nebo naopak
  • skript nebude kontrolovat, zda-li soubor skutečně existuje

Následující příklady ilustrují očekávané chování.

  • /etc/passwd/etc/passwd
  • a/b/././c/da/b/c/d
  • /a/b/../c/a/c
  • /usr/../etc//etc/

Můžete předpokládat, že jednotlivé komponenty cesty neobsahují znak nového řádku nebo další speciální znaky jako :, ", ' nebo nějakou escape sekvenci.

Nápověda: sed ':x; s/abb/ba/; tx' zajistí, že s/abb/ba/ je voláno opakovaně, dokud probíhá náhrada (:x definuje návěští a tx je podmíněný skok na návěští pokud předchozí nahrazování změnilo vstup). Vyzkoušejte s echo 'abbbb' | sed ....

Smyslem cvičení je prověřit vaše znalosti regexů, nikoli používání realpath nebo něčeho podobného.

Tento příklad vám může zkontrolovat GitLab skrz automatizované testy. Uložte vaše řešení jako 10/normalize.sh a commitněte ho (pushněte) do GitLabu.

Přepište úlohu netcfg.sh do jazyka Python a naučte se, jak se regulární výrazy používají v jazyce Python.

Tento příklad vám může zkontrolovat GitLab skrz automatizované testy. Uložte vaše řešení jako 10/netcfg.py a commitněte ho (pushněte) do GitLabu.

Balíček aspell poskytuje nástroj pro kontrolu pravopisu, který lze použít z příkazového řádku.

Příkaz aspell list --master en čte standardní vstup a všechna slova s překlepy vypisuje na standardní výstup.

Rozšiřte svůj projekt web a zkontrolujte, zda se ve zdrojových stránkách nevyskytují překlepy.

Tato úloha rozšíří průběžný příklad, který jsme použili na tomto cvičení.

Očekáváme, že soubory zkopírujte do vašeho repozitáře s úlohami, kde jsou i automatické testy (prosté zkopírování celého adresáře je v pořádku).

Následující jste již provedli (ale je to také součástí automatických testů k tomuto úkolu):

  1. Generujte soubory index.html a rules.html z příslušných souborů *.md.
  2. Vygenerované soubory uložte do podadresáře out/.
  3. cíl clean odstraní všechny soubory v adresáři out/ (kromě souboru .gitignore).

Jako novou funkci očekáváme, že příklad rozšíříte o následující:

  1. Přesuňte zdrojové soubory do podadresáře src/. Toto je povinná část, bez tohoto přesunu nebude žádný z testů fungovat. Očekáváme, že soubory přesunute ručně, tj. ne během buildu. Cílem je trochu pročistit adresářovou strukturu. Takže byste měli mít commitnutý soubor 10/web/src/index.md ve vašem repozitáři.

  2. Generujte stránky ze souborů *.csv. Ze score.csv se již generuje soubor score.html. Předpokládáme, že přidáte soubory group-a.csv a group-b.csv, ze kterých se generují group-a.html a group-b.html (pomocí skriptu table.py jako pro score.csv). Soubory group-a.html a group-b.html by měly být vytvořeny automaticky.

  3. Generujte stránky ze souborů *.bin. Očekáváme, že soubor bude mít stejné základní jméno jako výsledný soubor .html a postará se o kompletní generování obsahu. Test si vytváří vlastní from-news.bin, vaše řešení musí používat vzorová pravidla (pattern rules) se správými stemy.

    Příklad níže se naschvál jmenuje jinak (news.bin), aby nedocházelo ke kolizi se souborem připraveným v rámci testu.

  4. Přidejte speciální cíl spelling, který zobrazí seznam překlepů v souborech Markdown. Předpokládáme, že pro tento úkol použijete aspell a jako hlavní jazyk použijete angličtinu.

Nápověda #1: seznam generovaných souborů si uložte do proměnné PAGES, protože Vám to zjednoduší údržbu vašeho Makefile.

Nápověda č. 2: následující příklad je jednoduchý příklad dynamicky generované webové stránky, která může být uložena v souboru src/news.bin. Skript je trochu záludný, protože data webové stránky jsou součástí skriptu a používá $0 pro čtení sám sebe (podobný trik se často používá při vytváření samorozbalovacích archivů pro Linux).

#!/bin/bash

set -ueo pipefail

sed '1,/^---NEWS-START-HERE---/d' "$0" | while read -r date comment; do
    echo "<dt>$date</dt>"
    echo "<dd>$comment</dd>"
done | pandoc --template template.html --metadata title="News" -

exit 0

# Actual news are stored below.
# Add each news item on a separate line
#
# !!! Do not modify the line NEWS-START-HERE !!!
#

---NEWS-START-HERE---
2025-05-01 Website running
2025-05-02 Registration open

Tento příklad vám může zkontrolovat GitLab skrz automatizované testy. Uložte vaše řešení jako 10/web/Makefile a commitněte ho (pushněte) do GitLabu.

Převeďte shellový skript pro sestavení spustitelného souboru (ze zdrojových kódů C) na skript založený na make.

Zdrojáky jsou v repozitáři s příklady (v 10/cc).

Soubor Makefile, který vytvoříte, musí nabízet následující funkce:

  • Výchozí cíl all vytvoří spustitelný soubor example.
  • Speciální cíl clean odstraní všechny mezisoubory (*.o) i výsledný spustitelný soubor (example).
  • Objektové soubory (.o) se vytvářejí pro každý soubor zvlášť, doporučujeme použít vzorová pravidla (pattern rules).
  • Objektové soubory musí být závislé na zdrojovém souboru (odpovídajícím souboru .c) i na hlavičkovém souboru.

Prosím, commitněte do repozitáře i zdrojové soubory.

U složitějších projektů v jazyce C se často Makefile alespoň částečně generuje (včetně správných závislostí na vkládaných hlavičkových souborech). V této úloze očekáváme, že všechny závislosti zadáte ručně, abyste ukázali, že make ovládáte.

Existuje pouze jeden hlavičkový soubor, takže seznam závislostí bude ve skutečnosti poměrně krátký.

Tento příklad vám může zkontrolovat GitLab skrz automatizované testy. Uložte vaše řešení jako 10/cc/Makefile a commitněte ho (pushněte) do GitLabu.

Učební výstupy a kontrola po cvičení

Tato část podává zhuštěný souhrn základních konceptů a dovedností, které byste měli umět vysvětlit a/nebo použít po každém cvičení. Také obsahují absolutní minimum, které je potřebné pro pochopení navazujících cvičení (a dalších předmětů).

Znalosti konceptů

Znalost konceptů znamená, že rozumíte významu a kontextu daného tématu a jste schopni témata zasadit do většího rámce. Takže, jste schopni …

  • vyjmenovat několik kroků, které jsou často nutné k vytvoření distribuovatelného softwaru (např. balíček nebo instalační program) ze zdrojového kódu a dalších základních artefaktů

  • vysvětlit, proč by sestavování softwaru mělo být reprodukovatelným procesem

  • vysvětlit, jak je možné zachytit proces sestavení softwaru

  • vysvětlit pojmy jazyků, které se používají pro zachycení potřebných kroků pro sestavení (distribuci) softwaru

  • vysvětlit, co je to regulární výraz (regex)

Praktické dovednosti

Praktické dovednosti se obvykle týkají použití daných programů pro vyřešení různých úloh. Takže, dokážete …

  • sestavit projekty používající make

  • vytvořit Makefile, který řídí sestavení jednoduchého projektu

  • používat pravidla se zástupnými znaky v Makefile

  • volitelné: používat proměnné v Makefile

  • volitelné: používat základní rozšíření GNU Make pro zjednodušení složitějších Makefile

  • vytvářet a používat jednoduché regulární výrazy na filtrování textu grepem

  • provádět nahrazení vzoru pomocí sedu

Seznam změn na této stránce

  • 2025-04-08: Aktualizace příkazu na nastavení AFS.