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

Na začátku tohoto cvičení bude test, tématem je build systém make.

Test bude během první poloviny cvičení, prosím, přijďte na cvičení včas (detaily jsou na této stránce).

Jednotlivá témata v tomto cvičení na sebe nenavazují. Taky tady není žádný průběžný příklad a témata tedy můžete číst a zkoušet v libovolném pořadí.

Předstartovní kontrola

  • Chápete jak jsou navrženy a používány automatické (unit) testy.

Nástroje xargs (a parallel)

xargs ve své nejjednodušší podobě čte standardní vstup a převádí jej na argumenty pro uživatelem zadaný program.

Předpokládejme, že existují následující soubory:

2025-04-16.txt  2025-04-24.txt  2025-05-02.txt  2025-05-10.txt
2025-04-17.txt  2025-04-25.txt  2025-05-03.txt  2025-05-11.txt
2025-04-18.txt  2025-04-26.txt  2025-05-04.txt  2025-05-12.txt
2025-04-19.txt  2025-04-27.txt  2025-05-05.txt  2025-05-13.txt
2025-04-20.txt  2025-04-28.txt  2025-05-06.txt  2025-05-14.txt
2025-04-21.txt  2025-04-29.txt  2025-05-07.txt  2025-05-15.txt
2025-04-22.txt  2025-04-30.txt  2025-05-08.txt
2025-04-23.txt  2025-05-01.txt  2025-05-09.txt

Jako malé cvičení napište jednořádkový příkaz shellu, který tyto soubory vytvoří.

Řešení.

Naším úkolem je odstranit soubory starší než 20 dní. V této verzi pouze provedeme echo příkazu, abychom nemuseli soubory při ladění našeho řešení znovu vytvářet.

cutoff_date="$( date -d "20 days ago" '+%Y%m%d' )"
for filename in 202[0-9]-[01][0-9]-[0-3][0-9].txt; do
    date_num="$( basename "$filename" .txt | tr -d '-' )"
    if [ "$date_num" -lt "$cutoff_date" ]; then
        echo rm "$filename"
    fi
done

To znamená, že program rm by byl volán několikrát, přičemž by vždy odstranil jen jeden soubor. Režie spojená se spouštěním nového procesu by se mohla stát vážným úzkým hrdlem pro větší skripty (uvažujte například o tisících souborů).

Bylo by mnohem lepší, kdybychom rm zavolali jen jednou a zadali mu seznam souborů, které má odstranit (tj. jako více argumentů).

Zde je řešením xargs. Trochu program upravíme:

cutoff_date="$( date -d "20 days ago" '+%Y%m%d' )"
for filename in 202[0-9]-[01][0-9]-[0-3][0-9].txt; do
    date_num="$( basename "$filename" .txt | tr -d '-' )"
    if [ "$date_num" -lt "$cutoff_date" ]; then
        echo "$filename"
    fi
done | xargs echo rm

Místo toho, abychom soubor hned odstranili, vypíšeme pouze jeho název a celou smyčku přesměrujeme rourou do xargs, kde všechny jeho běžné argumenty odkazují na program, který má být spuštěn.

Místo mnoha řádků s rm ... uvidíme jen jeden dlouhý řádek s jediným voláním rm.

Další situací, kdy se xargs může hodit, je sestavování složitého příkazového řádku nebo situace, kdy by použití nahrazování příkazů ($( ... )) způsobilo nečitelnost skriptu.

Záludné názvy souborů mohou samozřejmě stále způsobovat problémy, protože xargs předpokládá, že argumenty jsou odděleny bílými znaky. (Všimněte si, že výše jsme byli v bezpečí, protože názvy souborů byly rozumné.) To lze změnit pomocí --delimiter.

Pokud do xargs přesměrováváte výstup z vašeho programu, zvažte oddělení položek nulovým bajtem (tj. terminátorem řetězce v jazyce C, \0). Vzpomeňte si, co jste měli v kurzu Arduina o řetězcích C – a jak se ukončují. To je nejbezpečnější možnost, protože tento znak se nemůže objevit nikde uvnitř žádného argumentu. A informujte o tom xargs pomocí -0 nebo --null.

Poznamenejme, že xargs je dostatečně chytrý na to, aby si uvědomil, kdy by byl příkazový řádek příliš dlouhý, a automaticky jej rozdělí (podrobnosti viz manuál).

Je také dobré mít na paměti, že xargs může příkaz provádět paralelně (tj. rozdělit stdin na více částí a zavolat program vícekrát s různými částmi) prostřednictvím -P. Pokud jsou vaše shellové skripty pomalé, ale máte dostatek procesorového výkonu, může vám to práci docela urychlit.

parallel

Tento program lze použít k paralelnímu provádění více příkazů, čímž se urychlí jejich provádění.

parallel se chová téměř stejně jako xargs, ale má mnohem lepší podporu pro souběžné provádění jednotlivých úloh (nemíchání jejich výstupů, provádění na vzdáleném stroji atd. atd.).

Rozdíly jsou poměrně dobře popsány v dokumentaci příkazu parallel.

Další podrobnosti naleznete v parallel_tutorial(1) (ano, je to manuálová stránka) a v parallel(1).

Správa uložiště II

Budeme pokračovat tam, kde jsme skončili ve cvičení 11.

Pokročilé připojování disků

Připojování disků není omezeno jen na ty fyzické. V další části budeme mluvit o obrazech disků, ale jsou i jiné možnosti. Je možné připojit síťový disk (třeba NFS nebo AFS, které je používané na MFF) nebo dokonce vytvořit síťové blokové zařízení a to připojit.

Pokud máte virtualizovaný Linux, např. skrz VirtualBox, připojování disků je trochu složitější. Můžete přidat další virtuální disk a ten připojit ručně. Nebo můžete vytvořit tzv. pass-through a dovolit virtuálnímu stroji přistupovat přímo k vašemu fyzickému disku. Například ve VirtualBoxu je možné přistupovat k fyzickému oddílu skutečného disku ale pro experimentování je asi bezpečnější začít s USB pass-through, které zpřístupní USB “flešku” uvnitř hosta. Ale vždy se ujistěte, že fyzické zařízení v ten okamžik nepoužívá hostitel.

Práce s obrazy disků (disk images)

Linux má zabudovanou podporu pro práci s obrazy disků. To jest, se soubory, které obsahují zrcadlový obraz skutečného disku. Ve skutečnosti jste s nimi už také pracovali, pokud jste instalovali Linux do virtuálky nebo si stáhly obraz USB disk na začátku semestru.

Linux vám umožní takovýto obraz připojit, jako by to byl fyzický disk a dokonce na něm měnit soubory. To je potřeba pro následující činnosti:

  • Vývoj a ladění souborových systémů (což je spíše neobvyklá činnost)
  • Vykopírování souborů z obrazů disků virtuálních strojů
  • Obnova souborů z poškozených disků (neobvyklé, ale k nezaplacení)
Při obnově dat z poškozených disků je obvyklý postup ten, že se pokusíte zkopírovat disk tak, jak je, na co nejnižší úrovni (tj. kopírujete prostě bajty z disku bez jakékoliv interpretace). A teprve poté, co máte zkopírovaný obraz, se pokusíte o vlastní záchranu dat. To zabrání dalšímu poškozování disku a dá vám to větší množství času na vlastní obnovu dat.

Ve všech případech ale pro připojení obrazu disku stačí systému říct, že má k souboru přistupovat jako k dalšímu blokovému zařízení (vzpomeňte si na /dev/sda1 z příkladu výše).

Určování svazků

Doposud jsme k určení svazku vždy používali název blokového zařízení (např. /dev/sdb1). Zatímco u malých systémů je to triviální, u větších to může být neuvěřitelně matoucí – názvy zařízení závisí na pořadí, v jakém systém disky objevil. Toto pořadí se může mezi jednotlivými spuštěními systému měnit a u vyměnitelných disků je ještě méně stabilní. Nechcete přece dopustit, aby náhodně připojený USB flash disk způsobil, že váš počítač nebude možné spustit :-).

Stabilnějším způsobem je odkazovat se na bloková zařízení pomocí symlinků pojmenovaných podle fyzického umístění v systému. Například /dev/disk/by-path/pci-0000:03:00.1-ata-6-part1 odkazuje na oddíl 1 disku připojeného k portu 6 řadiče SATA, který se nachází jako zařízení 00.1 na sběrnici PCI 0000:03.

Ve většině případů je ještě lepší popsat oddíl podle jeho obsahu. Většina souborových systémů má identifikátor UUID (univerzální jedinečný identifikátor, 128bitové číslo, obvykle náhodně generované) a často také štítek disku (krátký textový název). Můžete spustit lsblk -f pro zobrazení UUID a štítků všech diskových oddílů a poté zavolat mount s UUID=číslo nebo LABEL=štítek místo názvu blokového zařízení. Váš etc/fstab bude pravděpodobně odkazovat na vaše svazky jedním z těchto způsobů.

Připojování obrazů disků

Obrazy disků lze připojit téměř stejným způsobem jako bloková zařízení, pouze je třeba přidat k příkazu mount volbu -o loop.

Připomeňme, že mount vyžaduje práva roota (sudo), proto musíte následující příklad provést na svém vlastním počítači, nikoli na některém ze sdílených počítačů.

Chcete-li si to vyzkoušet, můžete si stáhnout tento FAT obraz disku a připojit jej.

sudo mkdir /mnt/photos-fat
sudo mount -o loop photos.fat.img /mnt/photos-fat
... (prace se soubory v /mnt/photos-fat)
sudo umount /mnt/photos-fat

Případně můžete spustit udisksctl loop-setup a přidat obraz disku jako vyměnitelnou jednotku, která se automaticky připojí na plochu:

# Pomoci udisksctl and automatickeho pripojovani v GUI
udisksctl loop-setup -f fat.img
# Tohle vytiskne asi /dev/loop0 ale cislo se muze lisit
# Ted to muzete pripojit v GUI (coz muze probehnout i automaticky)
... (prace se soubory v /run/media/$(whoami)/07C5-2DF8/)
udisksctl loop-delete -b /dev/loop0

Oprava poškozených disků

Pokud nejde disk připojit, ale je možné zkopírovat jeho obsah (obvykle by se použilo dd(1) ale cat /dev/sdX >image.raw může stačit), můžete se ho pokusit zachránit sami.

Hlavní nástroj systému Linux pro opravu poškozených svazků se nazývá fsck (kontrola souborového systému). Příkaz fsck je vlastně jednoduchý wrapper, který vybírá správnou implementaci podle typu souborového systému. Pro rodinu souborových systémů ext2/ext3/ext4 systému Linux se implementace nazývá e2fsck. Může být užitečnější volat přímo e2fsck, protože specializovanější volby se nepředávají prostřednictvím obecného fsck.

Jak jsme se již krátce zmínili výše, je bezpečnější pracovat na kopii svazku, zejména pokud máte podezření, že je svazek vážně poškozen. Tímto způsobem neriskujete jeho ještě větší poškození. To může být poměrně náročné z hlediska diskového prostoru: nakonec jde vždy o peníze – stojí data za víc než nákup dalšího disku nebo dokonce kompletní přenesení do profesionální firmy zaměřené na tento druh práce.

Případně můžete nejprve spustit e2fsck -n, který kontroluje pouze chyby, a sami posoudit jejich závažnost.

Někdy je disk příliš poškozený na to, aby ho fsck opravil. (Ve skutečnosti se to u souborových systémů ext stává zřídka – byli jsme svědky úspěšných oprav disků, jejichž prvních 10 GB bylo zcela přepsáno. Ale u souborových systémů DOS/Windows, jako jsou vfat a ntfs, jsou automatické opravy méně úspěšné.)

I v takovém případě je stále velká šance na obnovení mnoha souborů. Naštěstí, pokud nebyl disk příliš zaplněn, byla většina souborů ukládána průběžně. Můžeme tedy použít jednoduchý program, který prohledá celý diskový obraz a vyhledá signatury běžných formátů souborů (připomeňte si například, jak vypadá formát GIF). Tím samozřejmě neobnovíme názvy souborů ani hierarchii adresářů.

První program, který si ukážeme, je photorec (sudo dnf install testdisk). Před jeho spuštěním si připravte prázdný adresář, do kterého budete ukládat výsledky.

Přijímá jediný argument: soubor s diskovým obrazem, který se má skenovat. Poté spustí interaktivní režim, ve kterém vyberete, kam se mají obnovené soubory uložit, a také odhadnete typ souborového systému (ve většině případů to bude FAT nebo NTFS). Poté se pokusí soubory obnovit. Nic víc, nic míň.

photorec dokáže obnovit spoustu souborových formátů včetně souborů JPEG, MP3, ZIP (včetně ODT a DOCX) nebo dokonce RTF.

Dalším nástrojem je recoverjpeg, který se zaměřuje na obnovu fotografií. Na rozdíl od photorec pracuje recoverjpeg zcela neinteraktivně a nabízí několik dalších parametrů, které umožňují jemné nastavení procesu obnovy.

Balíček recoverjpeg není pro Fedoru připraven: můžete si ho zkusit nainstalovat ručně nebo si hrát pouze s photorec (a doufat, že ho nikdy nebudete potřebovat).

Zkoumání a úpravy svazků (oddílů)

Toto téma přenecháme více na pokročilý kurz. Pokud se chcete něco naučit sami, můžete začít s následujícími nástroji:

  • fdisk(8)
  • btrfs(8)
  • mdadm(8)
  • lvm(8)

Zkontrolujte si zda všemu rozumíte

Vyberte všechna pravdivá tvrzení. You need to have enabled JavaScript for the quiz to work.

Testování pomocí frameworku BATS

V této části stručně popíšeme BATS – testovací systém, který používáme pro automatické testy, které se spouštějí při každém odeslání do GitLabu.

Automatické testy jsou obecně jediným rozumným způsobem, jak zajistit, aby se váš software pomalu nekazil a nerozpadal. Dobré testy zachytí regrese, zajistí, aby se chyby neobjevovaly znovu, a často slouží jako dokumentace očekávaného chování.

Motto napište nejdříve testy se může často zdát přehnané a obtížné, ale obsahuje hodně pravdy (několik důvodů je uvedeno například v tomto článku).

BATS je systém napsaný v shellu, který se zaměřuje na shellové skripty nebo jakékoli programy s rozhraním CLI. Pokud znáte jiné testovací frameworky (e.g., Pythoní unittest, pytest nobo Nose), bude vám BATS pravděpodobně připadat velmi podobný a snadno použitelný.

Obecně platí, že každý testovací případ je jedna funkce shellu a BATS nabízí několik pomocných funkcí pro strukturování testů.

Podívejme se na příklad z domovské stránky BATS:

#!/usr/bin/env bats

@test "addition using bc" {
  result="$(echo 2+2 | bc)"
  [ "$result" -eq 4 ]
}

@test "addition using bc" je definice testu. BATS ji interně přeloží do funkce (můžete si to představit jako spuštění jednoduchého skriptu sedu nad vstupem a jeho odeslání do sh) a tělo je normální kód shellu.

BATS používá set -e k ukončení kódu, kdykoli se některý program ukončí nenulovým výstupním kódem. Pokud se tedy [ ukončí nenulou, test selže.

Kromě tohoto není v jeho základní podobě nic dalšího potřeba. I s touto základní znalostí můžete začít používat BATS k testování svých programů CLI.

Spuštění testů je jednoduché - udělejte soubor spustitelný a spusťte jej. Můžete si vybrat z několika výstupů a pomocí -f můžete filtrovat, které testy se mají spustit. Další podrobnosti najdete v bats --help nebo zde.

Komentovaný příklad

Napišme test pro naši funkci počítající faktoriál z cvičení 09 (funkci jste si vytvořili v jednom z příkladů).

Pro účely testování budeme předpokládat, že máme naši implementaci v souboru factorial.sh a naše testy vložíme do souboru test_factorial.bats.

Začneme špatnou implementací factorial.sh, abychom viděli, jak by měly být testy strukturovány.

#!/bin/bash

num="$1"
echo $(( num * (num - 1 ) ))

Naše první verze testu může vypadat takto.

#!/usr/bin/env bats

@test "factorial 2" {
    run ./factorial.sh 2
    test "$output" = "2"
}

@test "factorial 3" {
    run ./factorial.sh 3
    test "$output" = "6"
}

Ke spuštění našeho programu použijeme speciální příkaz BATS run, který zároveň zachytí jeho stdout do proměnné s názvem $output.

A pak jednoduše ověříme správnost.

Po spuštění příkazu se pravděpodobně vypíše něco jako toto (možná i barevně).

test_factorial.bats
 ✓ factorial 2
 ✓ factorial 3

2 tests, 0 failures

Přidáme další testovací případ:

@test "factorial 4" {
    run ./factorial.sh 4
    test "$output" = "20"
}

Ten neprojde, ale chybová zpráva není příliš užitečná.

test_factorial.bats
 ✓ factorial 2
 ✓ factorial 3
 ✗ factorial 4
   (in test file test_factorial.bats, line 15)
     `test "$output" = "20"' failed

3 tests, 1 failure

Důvodem je to, že BATS je velmi tenký framework, který v podstatě kontroluje pouze výstupní kódy a nic víc.

Ale můžeme to zlepšit.

#!/usr/bin/env bats

check_it() {
    local num="$1"
    local expected="$2"

    run ./factorial.sh "$num"
    test "$output" = "$expected"
}

@test "factorial 2" {
    check_it 2 2
}

@test "factorial 3" {
    check_it 3 6
}

@test "factorial 4" {
    check_it 4 24
}

Chybové hlášení není o moc lepší, ale test je takto mnohem čitelnější.

Výše uvedenou verzi si samozřejmě spusťte i sami.

Pojďme funkci check_it ještě trochu vylepšit.

check_it() {
    local num="$1"
    local expected="$2"

    run ./factorial.sh "$num"

    if [ "$output" != "$expected" ]; then
        echo "Factorial of $num computed as $output but expecting $expected." >&2
        return 1
    fi
}

Spusťte test znovu:

test_factorial.bats
 ✓ factorial 2
 ✓ factorial 3
 ✗ factorial 4
   (from function `check_it' in file test_factorial.bats, line 11,
    in test file test_factorial.bats, line 24)
     `check_it 4 24' failed
   Factorial of 4 computed as 12 but expecting 24.

3 tests, 1 failure

To poskytuje výstup, který je dostatečně dobrý pro ladění.

Přidání dalších testovacích případů je nyní hračka. Po tomto triviálním vylepšení začne naše sada testů skutečně dávat smysl.

A pomůže nám to.

Lepší testování (assertions)

BATS nabízí rozšíření pro psaní čitelnějších testů.

Místo přímého volání test tedy můžeme použít assert_equal, který vytvoří hezčí zprávu.

assert_equal "očekávaná-hodnota" "$skutecna"

Testy pro NSWI177

Naše testy obsahují rozšíření assert a několik našich vlastních. Všechny jsou součástí repozitáře, který se stahuje pomocí run_tests.sh ve vašich repozitářích.

Pokud chcete spustit jen určitý test lokálně (tj. ne na GitLabu), můžete soubor *.bats spustit přímo.

Ú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.

Účelem úlohy je vyzkoušet si psaní testů. Napíšete testy pro implementaci faktoriálu v shellu.

Faktoriál bude počítán funkcí factorial, které bere jeden argument a vytiskne spočítaný výsledek. Jako dobře vychovaná shellová funkce skončí při chybě s nenulovou návratovou hodnotou.

Vaše testy by měly být napsána proti specifikaci uvedené výše.

Naše testy pak do implementaci vloží různé chyby a očekáváme, že vaše testy tyto problémy odhalí. Nicméně, vy poskytnete jen jednu sadu testů, kterou my spustíme s různými implementacemi.

Vaše testy musí načíst (ve smyslu příkazu source) factorial.sh z aktuálního adresáře, který bude obsahovat vlastní implementaci funkce factorial v shellu.

Takže factorial.sh může pro začátek obsahovat následující (vaše testy by měly odhalit hodně problémů v tomto kusu kódu).

factorial() {
    local n="$1"
    echo $(( n * ( n - 1 ) ))
}

Následující kód je dobrým začátkem implementace tohoto úkolu.

#!/usr/bin/env bats

source "factorial.sh"

check_it() {
    local num="$1"
    local expected="$2"

    run factorial "$num"
    if [ "$status" -ne 0 ]; then
        echo "Function not terminated with zero exit code." >&2
        false
    fi
    if [ "$output" != "$expected" ]; then
        echo "Wrong output for $num: got '$output', expecting '$expected'." >&2
        false
    fi
}

@test "factorial 2" {
    check_it 2 2
}

@test "factorial 3" {
    check_it 3 6
}

@test "factorial 4" {
    check_it 4 24
}

Naše testy nebudou kontrolovat přesná chybová hlášení, ale budou kontrolovat, zda některé testy sady selhávají (pomocí návratového kódu BATS).

Podívejte se na implementaci testů pro snazší pochopení toho, co po vás chceme.

Netestujte pro čísla větší než 10.

Stojí za zmínku, že automatické testy jsou poměrně primitivní a pokud přidáte následující test, tak většina testů projde bez zvláštního úsilí. To nicméně není účelem úlohy.

@test "gaming the tests" {
    false
}

Tento příklad vám může zkontrolovat GitLab skrz automatizované testy. Uložte vaše řešení jako 14/factorial.bats 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 …

  • používat xargs

  • vysvětlit výhody používání automatizovaných testů funkčnosti

  • vysvětlit, co je to obraz disku

  • vysvětlit, proč nejsou zapotřebí žádné speciální nástroje pro práci s obrazy disků

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 …

  • spouštět testy založené na BATS

  • porozumět základní struktuře testů BATS

  • volitelné: použít lsblk ke zjištění informací o úložných zařízeních

  • volitelné: vytvářet jednoduché testy BATS

  • volitelné: opravit poškozené souborové systémy pomocí programů z rodiny fsck

  • volitelné: používat photorec k obnově souborů z poškozeného souborového systému