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

Na tomto cvičení probereme několik témat, která nejsou příliš rozsáhlá. Navíc spolu souvisí jen částečně, takže si můžete velké části přečíst v prakticky libovolném pořadí, které vám vyhovuje.

Podíváme se na uživatelské účty v Linuxu, zjistíme, jak se instaluje software a jak se spouštějí služby. Seznámíme se také s regulárními výrazy, které se používají k vyhledávání textových vzorů, a podíváme se, jak automaticky testovat naše programy pomocí BATS.

Uživatelské účty

Tomuto tématu jsme se již několikrát věnovali: berte ho také jako osvěžení věcí, které již znáte.

Uživatelské účty v systému Linux jsou dvou základních typů. Běžné uživatelské účty pro koncové uživatele, tj. účty, ke kterým se přihlašujete prostřednictvím SSH nebo grafického rozhraní a na kterých pracujete. Dále máme systémové účty, které existují výhradně za účelem možnosti spouštět procesy pod různými uživateli pro lepší izolaci. Pod těmito účty se obvykle vůbec nepřihlašujeme.

Vaše účty na linux.ms.mff.cuni.cz jsou prvního typu, a pokud spustíte ps -ef --forest, uvidíte, co mají spuštěno ostatní uživatelé. Systémové účty jsou například chrony nebo nginx, které slouží ke spouštění speciálních služeb systému.

Každý uživatelský účet má číselné id (podle kterého operační systém identifikuje uživatele) a uživatelské jméno, které je obvykle mapováno pomocí /etc/passwd.

Mezi uživatelskými účty na Linuxu má jeden z nich výsadní postavení. Tento uživatel se jmenuje root (často též superuser nebo superuživatel), má číselný kód 0 a má v podstatě neomezená oprávnění nad běžícím strojem. Například, přístupová práva jsou ve skutečnosti ignorována pro uživatele root (tj. proces běžící pod rootem ignoruje libovolné z rw oprávnění a může číst/zapisovat libovolný soubor).

Chcete-li přepnout na účet superuživatele, můžete použít buď příkaz sudo (viz níže), nebo su. Často se spouští takto, aby se zajistilo spuštění login shellu (mimo jiné se tím také zajistí, že $HOME ukazuje na /root a ne na domovský adresář běžného uživatele):

su -

Na rozdíl od jiných systémů, Linux je navržen tak aby uživatelské programy mohly vždy běžet pod normálním (obyčejným) uživatelem a nevyžadovaly rootovská oprávnění. Dokonce, některé programy (často se to týkalo třeba IRC klientů) se pod rootem odmítaly spustit.

Účet root je vyžadován pro změny v systému jako celku. To zahrnuje aktualizaci systému, formátování pevného disku nebo třeba modifikaci systémové konfigurace.

Velmi striktní oddělené běžných (pracovních) účtů a superuživatele vychází z faktu, že Linux byl navržen jako víceuživatelský systém. Tato filozofie je více jak 50 let stará a sahá do doby, kdy počítač byl sdílen mnoha uživateli a jen jeden z nich – root – byl administrátorem. Dnes, kdy typická notebooková instalace je právě pro jednoho uživatele, je toto rozdělení spíš umělé, ale pořád existuje.

Pravdou je, že dnešní uživatel je více ohrožen napadenou webovou stránku než neoprávněnou aktualizací systému a účet superuživatele byl navržen spíše s ohledem na to druhé. Nicméně, idea oddělených účtů dává stále smysl a obezřetný uživatel může používat různé účty pro různé aktivity (např. prohlížení sociálních sítí a práce s on-line bankovnictvím).

Správa uživatelských účtů

Považujeme ji za pokročilé téma a v tomto kurzu se omezíme na to, že vás odkážeme na dokumentaci příkazů useradd, userdel a usermod, které vytvářejí, odstraňují a upravují uživatelské účty.

Připomeňme, že pro získání informací o existujících účtech existuje také funkce getent.

Měli byste také znát passwd, který lze použít ke změně uživatelského hesla.

Spuštěním příkazu passwd se změní heslo aktuálního uživatele. Při spuštění pod uživatelem root můžeme zadat uživatelské jméno a změnit heslo jiného uživatele. U typické instalace je to způsob, jak snadno obnovit heslo.

sudo

Některé programy vyžadují zvýšení svých oprávnění (privilege escalation), tj. běžet s většími pravomocemi než ostatní programy. Některé to vyžadují svojí podstatou a už jsme zmiňovali set-uid bity na spustitelných souborech, které jsou použity u aplikací, které vždy vyžadují vyšší práva (bez ohledu na to, kdo je jak spustil).

Některé příkazy však vyžadují vyšší oprávnění jen občas, takže jejich spouštění jako set-uid zbytečně rozšiřuje možné vektory útoku.

Pro tyto situace je jedním z možných řešení sudo (domovská stránka). Jak jméno napovídá, spustí jeden příkaz (superuser do) s oprávněními superuživatele. Výhodou sudo je, že administrátor může určit který příkaz poběží s vyšším oprávněním. Takže nedává vybraným uživatelům kompletní kontrolu nad počítačem, ale pouze jim umožňuje pracovat s vybranou množinou příkazů.

Například je možné dát uživateli možnost restartovat určitou službu (např. chceme testerovi umožnit restartovat webový server), aniž bychom mu dali kontrolu nad celým počítačem.

Všimněte si, že granularita sudo je na úrovni programů. Neomezuje, jak se program chová uvnitř. Například, je možné omezit, že uživatelka alice může spustit nebezpecny_prikaz pouze s přepínačem --neskodny-prepinac. Ale, pokud nebezpecny_prikaz také čte konfiguraci ze souboru ~/.nebezpecnyrc, alice může skrz něj přidat volbu --nebezpecna-volba. A sudo tomu nemá jak zabránit. Jinými slovy: jakmile jsou počáteční kontroly (před spuštěním) dokončeny, program se chová jako by ho spustit uživatel root.

Tohle je velmi důležité pro sdílené stroje, kde administrátor chce typicky omezit ostatní uživatele jak nejvíce to jde. Na druhou stranu, na desktopových (notebookových) instalacích obvyklé nastavení je, že první vytvořený uživatel (typicky vytvořený ještě během instalace) může spustit libovolný program se sudo. Zdůvodnění je takové, že existuje stejně pouze jeden (fyzický) uživatel, který stejně rootovské heslo zná. To je také důvod, proč většina návodů na webu obvykle uvádí příkazy pro správu systému včetně sudo na začátku každého příkazu.

Měli byste ale vždy vědět, proč sudo pouštíte. Nechyťte se do pasti zvyku, že když to nefunguje, zkusím to pustit se sudo. Mimochodem, možností jak získat rootovský shell je více (např. sudo bash).

Jako bezpečný příklad můžete zkusit spustit fdisk -l a vypsat oddíly v systému. Při spuštění bez práv roota pravděpodobně selže s několika hlášeními o odepřeném přístupu. Spuštění se sudo by mělo fungovat.

Všimněte si, že zadáváte své heslo, nikoli heslo superuživatele (ostatně pokud by to bylo heslo superuživatele, nepotřebovali byste sudo, protože byste mohli spustit su - a získat root shell.

sudo fdisk -l

sudo není jediným bezpečnostním mechanismem. Ostatními se nebudeme do detailů zabývat, ale uvedeme alespoň odkazy na SELinux nebo AppArmor. Shrnutí mechanismů je k dispozici též na Wikipedii.

Přehled uživatelských účtů: zkontrolujte, zda rozumíte základům

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

Instalace softwaru (neboli správa balíčků)

Software v Linuxu je obvykle instalován prostřednictvím správce balíčků (package manager). Správce balíčků je speciální program, který se stará o instalaci, upgrade i odstranění balíčků. Balíček může být cokoliv, co lze nainstalovat; to zahrnuje:

  • programy (například, balíček ranger nainstaluje program ranger),
  • datové soubory a nastavení (např. libreoffice-langpack-cs pro podporu češtiny v LibreOffice),
  • knihovna (třeba gmp nebo gmp-devel poskytují GNU knihovnu pro libovolně přesné výpočty),
  • nebo tzv. meta balíček (např. xfce zahrnuje xfce4-terminal, xfwm4-themes atd.).

V tomto směru je Linux velmi podobný tomu, co znáte ze správy softwaru pomocí obchodu na vašich chytrých telefonech. Je velmi neobvyklé instalovat software na Linuxu prostřednictvím grafického instalátoru.

Výhodou centrální správy balíčků je možnost upgradovat celý systém, aniž by bylo nutné kontrolovat samostatně jednotlivé aplikace.

Jednotlivé balíčky mají často závislosti (dependencies) – instalace jednoho balíčku způsobí tranzitivní instalaci ostatních balíčků, na kterých závisí (například, webový prohlížeč bude vyžadovat základní podporu GUI apod.). To trochu komplikuje proces aktualizací (pro správce balíčků, naštěstí nikoliv pro uživatele). Ale ušetří to trochu místa na disku. Asi nejdůležitější výhodou je, že různé aplikace mohou sdílet stejné knihovny (na Linuxu mají příponu .so a jsou podobné souborům DLL na Windows). Takže je možné upgradovat knihovnu i dále nevyvíjené aplikaci. Což se velmi hodí, pokud knihovna obsahuje bezpečnostní záplatu.

Je pochopitelně možné instalovat software i ručně. Z pohledu souborového systému je to jedno – správce balíčků v podstatě jen kopíruje soubory do správných adresářů. Ale ručně nainstalovaný software musí být i ručně aktualizován a věci spíš komplikuje. Takže se tomu pokud možno vyhýbejte.

Typický správce balíčků pracuje s několika repozitáři softwaru (software repository). Můžete o nich uvažovat jako by váš telefon měl několik obchodních center, kde si aplikace vybíráte. Obvykle najdete následující typy repozitářů. Je na uživateli (správci), které repozitáře bude využívat.

  • Stable a testing, kde ten druhý nabízí novější verze softwaru ale s drobnou možností výskytu chyb (obvykle existuje ještě třetí repozitář, obvykle nazvaný unstable, který je pro poslední, často vývojové, verze).
  • Free a non-free kde ten první obsahuje jen software bez právních překvapení. Non-free software může být zatížen patenty nebo vlastnickými právy (obvykle podle práva USA) nebo licencemi, které omezují jeho šíření.

Můžete si vytvořit i vlastní repozitář, což se může hodit když potřebujete instalovat software na více strojů (třeba jde o proprietární software, který máte zabalíčkovaný, ale nemůžete ho poslat do veřejných repozitářů).

Velká většina distribucí také nabízí nějaký druh uživatelských repozitářů, kde v podstatě kdokoliv může zveřejnit svůj software. Pro Fedoru existuje Copr.

Ani oficiální ani neoficiální repozitáře neposkytují žádnou záruku v právním slova smyslu. Nicméně, používání oficiálních (výchozích) repozitářů se považuje za bezpečné, množství útoků na tyto repozitáře je nízké a – na rozdíl od mnoha komerčních institucí – správci těchto repozitářů jsou velmi otevření v informování o podobných incidentech. Je mnohem snazší narazit na podvodnou aplikaci v “obchodu” pro váš chytrý telefon než na ní narazit v oficiálních repozitářích vaší distribuce.

dnf (tj. správce balíčků pro Fedoru)

Fedora dříve používala jako správce balíčků yum a lze jej nalézt v mnoha návodech na internetu (dokonce i v poměrně nedávných). Nyní je považován za zastaralý a raději se mu vyhněte.

Pokud jste zvyklí na yum ze starších verzí Fedory nebo z jiných distribucí založených na RPM, zjistíte, že dnf je velmi podobný a v mnoha situacích rychlejší než yum.

Správce balíčků pro Fedoru se nazývá DNF.

Pokud jste se rozhodli používat jinou distribuci, budete muset příkazy upravit tak, aby odpovídaly vašemu systému. Obecně by operace měly být dost podobné, ale nemůžeme zde poskytnout návod pro každého správce balíčků.

Pomocí příkazu search můžete získat seznam balíčků, které odpovídají zadanému názvu. Všimněte si, že vyhledávání není privilegovanou operací, a proto nevyžaduje sudo.

dnf search arduino
dnf search atool

Všimněte si, že vyhledávání velmi obecného výrazu může přinést stovky výsledků.

Výstup je v následujícím formátu:

atool.noarch : A perl script for managing file archives of various types
ratools.x86_64

Řetězce .noarch a .x86_64 popisují povahu balíčku. noarch obvykle označuje datový balíček nebo balíček používající interpretované jazyky, zatímco .x86_64 označuje balíček s binárními soubory pro architekturu x86-64 (např. napsaný v jazyce C nebo Rust a následně zkompilovaný do strojového kódu).

Chcete-li nainstalovat softwarový balíček, spusťte dnf s příkazem install a zadejte mu název balíčku, který chcete nainstalovat. Zde už je nutné sudo, protože upravujeme systém.

sudo dnf install atool

Některé aplikace nejsou součástí žádného softwarého repozitáře, ale přesto je můžete stáhnout ve formátu srozumitelném správci balíčků. To je lepší situace než instalovat soubory ručně, protože váš správce balíčků o nich (i když je nemůže automaticky aktualizovat). Jedním z takových příkladů je klient Zoom, který se musí nainstalovat takto:

sudo dnf install "https://zoom.us/client/latest/zoom_x86_64.rpm"

Chcete-li aktualizovat celý systém, jednoduše spusťte sudo dnf upgrade. DNF si vyžádá potvrzení a poté provede aktualizaci všech dostupných balíčků.

Všimněte si, že na rozdíl od jiných systémů si můžete vždy vybrat, kdy chcete provést upgrade. Systém za vás nikdy nerestartuje počítač ani nezobrazí zprávu o nutném restartu, pokud o to výslovně nepožádáte.

Pokud chcete nainstalovat celou skupinu balíčků, můžete použít dnf grouplist pro zobrazení jejich seznamu a sudo dnf install @GROUP_NAME pro jejich instalaci.

Výše uvedené příkazy obsahují základní informace o správě balíčků v systému Fedora. Následující odkazy poskytují další informace. Pokud již systém trochu znáte, je dobrým zdrojem informací oficiální stránka Wiki.

Pro začátečníky jsou pravděpodobně lepším výchozím bodem tento průvodce DNF a tento návod.

Náhrady klasických správců balíčků

Přítomnost různých správců balíčků má i nevýhody – pokud používáte více distribucí, musíte umět používat více programů. Navíc, různé distribuce musí vytvářet svoje vlastní balíčky (kompatibilní se svými správci balíčků) což je opět více práce.

Existuje proto i snaha správce balíčků sjednotit. Snap byl vytvořen, aby bylo možné jednotným způsobem instalovat balíčky napříč distribucemi. Zatímco pro některé uživatele je to snadný způsob jak rychle software nainstalovat, pro jiné je proprietární podstata Snapu a nutnost mít uživatelský účet překážka, potenciální riziko a odklon od otevřenosti.

Jako problematický příklad se můžeme podívat na instalaci PyCharmu. PyCharm je IDE pro Python, které (bohužel) cílí na uživatele Windows a také nabízí placenou profesionální verzi. Balíček PyCharmu pro Fedoru neexistuje.

Je to ale spíše výjimka – tento problém u většiny open-source programů nenajdete. Dokonce i společnosti zaměřené na jiné operační systémy dnes nabízí repozitáře pro DNF obsahující jejich software. Všimněte si, že v tomto případě je nabídka kompletního repozitáře ideální volbou. Uživatelé si mohou vybrat, jestli takový repozitář chtějí nebo ne, správci distribuce to nemusí dále řešit a komerční společnost má stále plnou kontrolu na vlastní distribucí.

Jsou tedy dvě možnosti jak nainstalovat PyCharm:

  1. Použít Snap
  2. Použít ad-hoc instalační skript, který je stažen spolu s instalací PyCharmu.

Druhá možnost není příliš oblíbená. Vyžaduje, aby uživatel spustil skript, který si stáhl, což je potenciálně nebezpečné – vždy byste měli podobné skripty kontrolovat. (Samozřejmě, i správce balíčků stahuje a spouští skripty, ale možnosti útoku jsou trochu menší.)

Dalším problémem je, že aplikace nainstalovaná tímto způsobem nemůže být automaticky aktualizovaná.

Co použít

Snap není jediná alternativa ke klasickým správcům balíčků.

Existuje také Flatpak nebo AppImage. Mohou existovat vedle sebe a je na uživateli, který si vybere.

Rozhodnutí, který používat, je ovlivněno mnoha faktory. Obecně je nejlepší preferovat instalaci přes obvyklé nástroje vaší distribuce.

Jako poslední poznámku: pokud software který potřebujete není jako balíček dostupný, můžete si vždy takový balíček sami vytvořit. Postup je mimo záběr předmětu, ale není to tak složité.

Správa balíčků: zkontrolujte si, zda této části rozumíte

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

Služby (a démoni) (services, daemons)

V kontextu operačních systémů, termínem služba (service) odkazujeme na libovolný program, který běží na pozadí (takže žádné GUI, stdin z /dev/null) a poskytuje nějakou službu dalším programům.

Typickým příkladem může být služba tisku, která se stará o nalezení tiskáren a koncovým aplikacím poskytuje jejich seznam (tj. uživatelské aplikace nemusí tiskárny samy vyhledávat). Jiný příklad je webový server: poskytuje soubory přes HTTP protokol webovým prohlížečům.

Ve světě Unixových systémů jsou tyto programy často označovány jako démoni (což asi naráží na řeckou mytologii, kde démon bylo stvoření pracující v pozadí); tradičně názvy těchto programů končí písmenem d. Například, populární webový server Apache je ve skutečnosti spouštěn jako program httpd a SSH server běží jako sshd.

Démoni fungují jinak než normální programy. Když jsou spuštěny, přečtou si konfiguraci (typicky ze souborů pod /etc/), spustí se a čekají na požadavky (představte si webový server poslouchající na portu 80). Změna jejich chování je obvykle provedena změnou konfiguračního souboru a jejich restartováním. Protože běží na pozadí, nemají přístup k interaktivnímu stdinu a restart (nebo ukončení) se provádí pomocí signálů.

Připomeňme, že jsme již dříve viděli nástroj kill pro zastavení programů. Tato utilita je ve skutečnosti univerzálnější, protože může také vyslat signál, který může program zachytit a může na něj reagovat (podrobnosti uvidíme později). Takovým příkladem je reakce na speciální signál, který dává programu pokyn, aby znovu načetl svou konfiguraci.

Protože požadavek na restart démona je poměrně běžný (a posílání signálů je těžkopádné kvůli nutnosti zjišťovat PID), existují i speciální programy, které si PID najdou a pošlou ten správný signál. Obvykle se nazývají řídící skripty a pro některé démony najdete soubory serviced (vlastní démon) a servicectl pro jeho řízení.

Sjednocené řízení démonů

Výše uvedené principy jsou v podstatě stejné pro všechny služby, proto obvykle existuje sada skriptů, která toto chování sjednocuje. Takže místo toho, abyste museli volat daemonctl, distribuce vám nabídne speciální příkaz, kterým můžete ovládat libovolnou službu. Obvykle tedy použijete něco jako toto:

service [start|stop|restart] jmeno-daemona

V současnosti je nejobvyklejším nástrojem pro tuto úlohu systemd.

O logování

Většina služeb poskytuje tzv. logy (protokoly). Obsahují záznam o každé důležité akci, kterou démon provedl.

Například, webový server typicky zaznamená, které stránky obsloužil společně s informací o klientovi.

Obvykle pro každou služba určíte, jak má být logování detailní. Ladění problému v konfiguraci vyžaduje detailní zprávy, v produkčním prostředí naopak zapnete jen logování vážných chyb pro zvýšení výkonu.

Systemd

Systemd je jedním z nejpoužívanějších nástrojů pro správu systémových služeb v dnešním světě Linuxu.

Nebudeme zabíhat do podrobností a pouze si projdeme dva nejdůležitější příkazy: systemctl a journalctl.

Všimněte si, že systemd je démon, zatímco systemctl je příkaz pro jeho ovládání.

Spuštění a zastavení služby

Spuštění služby pomocí systemd je velmi jednoduché. Následující příkaz spustí sshd, server SSH:

sudo systemctl start sshd

Pokud služba již byla spuštěna, nic se nestane.

Zkontrolujte, zda se nyní můžete připojit ke svému počítači pomocí následujícího příkazu:

ssh your-login@localhost

Ke zjištění stavu služby se používá příkaz status (ten lze spustit i bez sudo, ale může zobrazit méně informací):

sudo systemctl status sshd
● sshd.service - OpenSSH Daemon
     Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: disabled)
     Active: active (running) since Mon 2021-03-01 14:31:40 CET; 2 months 3 days ago
   Main PID: 560 (sshd)
      Tasks: 1 (limit: 9230)
     Memory: 900.0K
        CPU: 16ms
     CGroup: /system.slice/sshd.service
             └─560 sshd: /usr/bin/sshd -D [listener] 0 of 10-100 startups

Warning: journal has been rotated since unit was started, output may be incomplete.

Vidíme, že služba je spuštěna, většina položek je jasná. Soubor /usr/lib/systemd/system/sshd.service obsahuje samotnou konfiguraci služby (např. jak službu spustit/zastavit/restartovat), nikoli vlastní konfiguraci démona SSH, která je uvnitř /etc/ssh.

Pokud démona SSH na notebooku nebudete používat, je bezpečnější ho zastavit:

sudo systemctl stop sshd

Povolení a zakázání služby

Pokud chcete službu spouštět při každém spuštění systému, můžete ji povolit:

sudo systemctl enable sshd

Systemd se postará o správné pořadí jednotlivých služeb (takže SSH server se spustí až po inicializaci sítě atp.).

Pokud si již nepřejete, aby byl démon SSH automaticky spuštěn, zavolejte příkaz s disable.

Všimněte si, že jak enable, tak disable nemění aktuální stav služby: pokud nechcete čekat na restart, musíte stále použít start/stop. (Pro větší pohodlí je k dispozici příkaz systemctl enable --now sshd, který službu také spustí.)

Logy

Většina systémových služeb uchovává logy o své práci. Ty jsou obvykle uloženy v adresáři /var/log/. Některé služby si je vytvářejí samy a jde o jednoduché textové soubory, jejichž formát je specifický pro jednotlivé služby a jejich konfiguraci.

Mnoho služeb ale používá centrální logovací službu, která uchovává všechny své logy v jednotném formátu a kterou lze nakonfigurovat pro jejich třídění, odesílání po síti, odstraňování starých záznamů atp.

Ve Fedoře se logovací služba nazývá journald. Ukládá protokoly do kryptograficky podepsaných binárních souborů, které nejsou přímo čitelné. Lze je číst pomocí příkazu journalctl.

Následující příkaz například zobrazí logy démona SSH:

journalctl -u sshd

Více …

Pokud vás toto téma zajímá, přečtěte si příslušné manuálové stránky. Těchto několik odstavců berte jako velmi stručný úvod do tématu, který vám umožní základní správu systému.

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. (Legenda říká, že g ve jméně znamená “globally”, re je regex a p print.) Spustíme-li grep regex soubor, vypíší se všechny řádky souboru, které odpovídají zadanému regulárnímu výrazu. Během cvičení si vyzkoušíme spoustu příkladů.

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ší.

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:

^[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

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

Cvičení

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

Solution.

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

Solution.

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

Solution.

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

Solution.

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.

Solution.

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

Solution.

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

Solution.

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

Solution.

Odstraňte všechny prázdné řádky. Hint.

Solution.

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

Solution.

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í 08.

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 před cvičením (deadline: začátek vašeho cvičení, týden 17. dubna - 21. dubna)

Následující úlohy musí být vyřešeny a odevzdány před příchodem na vaše cvičení. Pokud máte cvičení ve středu v 10.40, soubory musí být nahrány do vašeho projektu (repozitáře) na GitLabu nejpozději ve středu v 10.39.

Pro virtuální cvičení je deadline úterý 9:00 (každý týden, vždy ráno, bez ohledu na možné státní svátky apod.).

Všechny úlohy (pokud není explicitně uvedeno jinak) musí být odevzdány přes váš repozitář na úkoly. Pro většinu úloh existují automatické testy, které vám mohou pomoci zkontrolovat úplnost vašeho řešení (zde je popsáno jak číst jejich výsledky).

10/uid.txt (20 bodů, skupina admin)

Do tohoto souboru vložte (číselné) uživatelské ID svého účtu na linux.ms.mff.cuni.cz.

Tato úloha je pouze částečně testována automatickými testy.

10/fdisk.txt (30 bodů, skupina admin)

Tuto úlohu proveďte na vlastním počítači.

Uložte do souboru seznam diskových oddílů počítače, jak je vypsal fdisk -l.

Ujistěte se, že jste uložili celý výstup a nic nekopírovali ručně (tj. použijte přesměrování).

Aby tato úloha fungovala, musí být spuštěna pod uživatelem root.
Tato úloha je pouze částečně testována automatickými testy.

10/netcfg.sh (20 bodů, skupina net)

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. Poznamenejme, že tento případ je ohodnocen pouze 5 body, protože těžiště úlohy spočívá v sestavení správného regulárního výrazu v sedu, který odpovídá příslušným řádkům (použití sedu není povinné, ale pro tuto úlohu se doporučuje).

10/normalize.sh (30 bodů, skupina shell)

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.

Úlohy po cvičení (deadline: 14. května)

Očekáváme, že následující úlohy vyřešíte po cvičení, tj. poté, co budete mít zpětnou vazbu k vašim řešením úloh před cvičením.

Všechny úlohy (pokud není explicitně uvedeno jinak) musí být odevzdány přes váš repozitář na úkoly. Pro většinu úloh existují automatické testy, které vám mohou pomoci zkontrolovat úplnost vašeho řešení (zde je popsáno jak číst jejich výsledky).

10/factorial.bats (40 bodů, skupina devel)

Poznámka: popis úlohy byl přepsán, aby lépe zachytil její účel. Také jsme aktualizovali výchozí implementaci, aby používala rozumné chybové hlášky a také kontroluje proměnnou $status, do které BATSová funkce run uloží exit kód spuštěného programu.

Úč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 a nebudeme to brát jako správné řešení.

@test "gaming the tests" {
    false
}

10/netcfg.py (40 bodů, skupina devel)

Přepište příslušnou úlohu před cvičením do jazyka Python a naučte se, jak se regulární výrazy používají v jazyce Python.

Bodové hodnocení jednotlivých testů bylo upraveno ale jinak jsou testy v podstatě totožné.

10/uname.txt (20 bodů, skupina admin)

Nainstalujte do počítače jazyk Ruby.

Pak na svém počítači spusťte následující skript v jazyce Ruby a vložte jeho výstup do souboru 10/uname.txt.

Pravděpodobně budete muset přidat správný shebang a spustitelný bit nebo explicitně spustit interpretr Ruby nad tímto zdrojovým kódem.

require 'etc'

puts Etc.uname
Spusťte tento příkaz na svém počítači. Pro tento úkol nepoužívejte IMPAKT/Rotundu nebo linux.ms.mff.cuni.cz.
Tato úloha je pouze částečně testována automatickými testy.

Učební výstupy

Učební výstupy podávají 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 …

  • vysvětlit, jak a proč je software distribuován ve formě balíčků

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

  • vysvětlit rozdíl mezi účtem root a ostatními účty

  • vysvětlit, proč se obecně nedoporučuje provádět neadministrativní úlohy pod účtem root

  • obecně vysvětlit, jak lze sudo použít pro správu systému

  • porozumět nebezpečím používání sudo

  • vysvětlit, co je to služba (démon)

  • vysvětlit životní cyklus a možné stavy služeb

  • vysvětlit, co je to protokol programu a jak jej lze spravovat

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

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 …

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

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

  • používat getent na získání informací o existujících uživatelských účtech

  • používat sudo pro zvýšení oprávnění spouštěného programu

  • používat správce balíčků (package manager) pro instalaci a odinstalaci balíčků

  • používat správce balíčků (package manager) pro upgrade celého systému

  • používat systemctl ke spuštění/zastavení služeb

  • používat systemctl pro zajištění automatického spuštění služby při startu počítače

  • volitelné: používat journalctl k zobrazení logů

  • volitelné: používat useradd pro vytvoření nového uživatelského účtu

  • volitelné: používat SANE pro přístup ke skenerům v Linuxu

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

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

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

Seznam změn na této stránce

  • 2023-04-14: Přidány automatické testy pro úlohy před cvičením.

  • 2023-04-14: Varování ohledně návratové hodnoty grepu.

  • 2023-04-20: Rozšířen příklad u hodnocené úlohy po cvičení s faktoriálem.

  • 2023-04-20: Přidány automatické testy pro úlohy po cvičení.

  • 2023-04-21: Další poznámka k bodované úloze o testování.

  • 2023-04-26: Přepsána úloha factorial.bats task a posunutí termínu odevzdání.