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

Cílem tohoto cvičení je definovat a do hloubky pochopit, co je to standardní vstup (stdin), standardní výstup (stdout) a standardní chybový výstup (stderr). To nám umožní porozumět tomu, jak funguje přesměrování vstupu a výstupu (I/O redirection) a propojování programů pomocí pipes (český ekvivalent “pipe” je doslova “roura”, ale tento pojem se v praxi téměř nepoužívá). Také si přizpůsobíme chování našeho shellu - prozkoumáme, jak fungují aliasy a .bashrc.

Průběžný příklad

Toto cvičení bude postaveno na jednom příkladu, který budeme postupně rozvíjet, abyste se základní koncepty naučili na praktickém příkladu (samozřejmě existují specifické nástroje, které by se daly použít rovnou, ale doufáme, že je to lepší než zcela umělý příklad).

Data pro náš příklad lze stáhnout (tj. git cloneovat) z tohoto repozitáře, kde se nacházejí v podadresáři 04/.

Simulují zjednodušené logy webového serveru, kde webový server zaznamenává, ke kterým souborům (URL) bylo přistupováno v jakém čase.

Každý soubor obsahuje provoz za jeden den ve zjednodušeném formátu CSV.

Pole jsou oddělena čárkou, není zde žádná hlavička a u každého záznamu si pamatujeme datum, IP adresu klienta, adresu URL, která byla vyžádána, a množství přenesených bajtů.

Ve skutečnosti by data byla komprimována a pravděpodobně by obsahovala více podrobností o klientovi (např. použitý prohlížeč), ale jinak zaznamenaná data představují poměrně typický formát logů webového serveru.

Naším úkolem je napsat program, který vypíše stručný přehled těchto dat:

  1. Vytiskne 3 nejčastěji navštěvované adresy URL.
  2. Vytiskne 3 dny s největším objemem provozu (tj. součtem přenesených bajtů).
  3. Vytiskne celkového množství přenesených dat.

Než začneme úlohu řešit, musíme se naučit úplné základy.

Standardní vstup a výstup

Cvičení začneme několika definicemi, které ale již pravděpodobně znáte (ale možná ne přesně pod těmito názvy).

Standardní výstup

Standardní výstup (obvykle se mu říká stdout, což je zkratka ze “standard output”) se použije, když zavoláte print("Hello") třeba v Pythonu - je to cíl, kam se vytiskne výstup vašeho programu. Stdout používají prakticky všechny základní výstupní funkce (funkce, které se používají třeba pro vytištění textu na obrazovku) v téměř každém programovacím jazyce.

V zásadě se stdout chová jako soubor - říkáme, že má stejné API pro zápis, jako obyčejný soubor. Proto se na něj zapisuje stejně bez ohledu na to, z jakého jazyka - print z Pythonu, System.out.print v Javě i printf v Céčku (kde existuje pár funkcí print a printf kvůli vlastnostem C, do kterých nemá smysl zde zabíhat) - všechna tahle volání se k stdoutu chovají stejně. Dokonce se k němu chovají v zásadě stejně bez ohledu na to, zda stdout míří do souboru, je to terminál (takže “píše na obrazovku”) nebo je vstupem jiného programu.

Když se v Linuxu spustí program, dostane stdout už připravený. O to se postará shell ve spolupráci s operačním systémem, někdy se toho účastní i runtime jazyka, ve kterém byl program napsán. Ani zde nemá smysl zabíhat do podrobností; v praxi je pouze důležité vědět, že stdout dostane program už připravený a co napíšeme na stdout, objeví se na obrazovce v terminálu (a pokud jsme aplikaci spustili graficky, většinou o data zapsaná na stdout přijdeme).

V Pythonu můžete k otevřenému souboru, který reprezentuje stdout, přistoupit pomocí sys.stdout. Jedná se o stejný objekt, jaký vrací volání open.

Standardní vstup

Podobně jako můžete zapisovat na stdout v téměř každém jazyce, můžete číst ze standardního vstupu (kterému se říká stdin ze standard input). Obvykle lze ze stdin přečíst to, co napíšete na klávesnici do terminálu (a pouze do terminálu, grafické aplikace se ke klávesnici chovají jinak).

Všimněte si, že funkce input(), kterou jste asi v Pythonu používali je nadstavbou stadnardního vstupu, protože umožní vstup upravovat. Obyčejný stdin nepodporuje žádné úpravy (resp. umí mazat znaky od konce řádku).

Pokud chcete v jazyce Python přistupovat ke standardnímu vstupu, musíte explicitně použít sys.stdin. Jak se dalo očekávat, používá souborové API, a proto je možné z něj přečíst řádek voláním .readline() nebo iterovat přes všechny řádky.

Iterace přes všechny řádky vstupu je velmi běžné chování, které je společné mnoha Linuxovým programům (ty jsou obvykle napsané v Céčku, ale chovají se stejně).

for line in sys.stdin:
    ...
Všimněte si, že tahle smyčka funguje stejně dobře s libovolným otevřeným textovým souborem v Pythonu, a textový vstup se takhle skutečně obvykle zpracovává.

Pro mnoho nástrojů je ve skutečnosti čtení ze stdin výchozím chováním. Například cut -d : -f 1 vypíše pouze první sloupec dat z každého řádku (a očekává, že sloupce budou ohraničeny znakem :).

Spusťte jej a napište na klávesnici následující, přičemž každý řádek ukončete znakem <Enter>.

cut -d : -f 1
one:two
alpha:bravo
uno:dos

Pod zadaným vstupem by se měl zobrazit první sloupec.

Co dělat, když jste hotovi? Zadání exit zde nepomůže, ale <Ctrl>-D zafunguje.

Stisk <Ctrl>-D na prázdné řádce způsobí ukončení standardního vstupu. Program cut si všimne, že již nemůže zpracovávat žádný vstup a ukončí se. Pozor, jedná o něco jiného, než kombinace kláves <Ctrl>-C, která násilně zabije běžící proces. Z uživatelského pohledu to při použití nástroje cut vypadá podobně, ale jedná se o jiné chování a rozdíl je zásadní (a u jiných utilit může být i viditelný).

Přesměrování vstupu a výstupu (“I/O redirection”)

Jako technický detail jsme již zmínili, že standardní vstup a výstup připravuje (částečně) operační systém. To také znamená, že je lze změnit (tj. jinak zinicializovat), aniž by se změnil program. A program o tom ani nemusí “vědět”.

Když změníme standardní vstup nebo výstup našeho programu, říká se tomu přesměrování (standardního vstupu resp. standardního výstupu). Přesměrování nám například umožňuje vyjádřit, že výstup se nemá objevit na obrazovce, ale má být uložen do souboru.

Přesměrování musí proběhnout předtím, než se program spustí, a musí se o něj postarat ten, kdo program spouští - tedy shell, což je pro nás v tuto chvíli podstatné.

Zařídit přesměrování je jednoduché: na konec příkazu můžeme napsat třeba > output.txt a všechno, co by se normálně objevilo na obrazovce v terminálu, se teď objeví v souboru output.txt.

Než začnete experimentovat: přesměrování výstupu je nízkoúrovňová operace a nemá žádnou formu “kroku zpět” (ve smyslu undo). Pokud tedy soubor, na který přesměrováváte, již existuje, bude bez dalšího ptaní přepsán. A to bez jakékoliv snadné možnosti obnovení původního obsahu (a u malých souborů je obnovení u většiny souborových systémů používaných v Linuxu technicky nemožné).

Pro jistotu si zvykněte po zadání názvu souboru stisknout klávesu <Tab>. Pokud soubor neexistuje, kurzor se nepohne. Pokud soubor již existuje, doplnění tabulátorem vloží mezeru.

Jako nejjednodušší příklad můžeme spustit následující dva příkazy, které vytvoří soubory one.txt a two.txt, ve kterých bude po řadě napsáno ONE resp. TWO (a znak konce řádku jako poslední znak).

echo ONE > one.txt
echo TWO >two.txt

Syntaxe shellu je poměrně volná, takže je možné vynechat mezeru před one.txt a napsat >one.txt. Mezera za znakem > tedy není součástí názvu souboru, do kterého přesměrováváme.

Z pohledu implementace: echo dostalo jediný argument. Část s přesměrováním (> filename) mu nebyla nijak předána a > filename budete v sys.argv tudíž hledat marně.

Pokud znáte volání popen z Pythonu, možná víte, že umožňují specifikovat, co se má použít jako stdout. To se hodí právě pro přesměrování v nově spouštěných programech (už běžícím programům se takhle stdin/stdout přesměrovat nedá).

Ve druhém cvičení jsme se zmínili, že program cat se používá na “zřetězování” souborů (vytištění několika souborů za sebou). Teď, když umíme přesměrovat výstup, začíná to dávat smysl: výstup cat, tedy několik souborů vytištěných po řadě za sebou, se dá jednoduše uložit do nového souboru.

cat one.txt two.txt >merged.txt

Připojování výstup na konec při přesměrování

Shell také nabízí možnost připojit výstup k existujícímu souboru pomocí operátoru >>. Následující příkaz tedy přidá UNO jako další řádek do souboru one.txt.

echo UNO >>one.txt

Pokud soubor neexistuje, bude vytvořen.

Pro následující příklad budeme potřebovat program tac, který obrací pořadí jednotlivých řádků, ale jinak funguje jako cat (všimněte si, že tac je cat, ale obráceně, což je vážně skvělý název). Nejprve si vyzkoušejte tento postup.

tac one.txt two.txt

Pokud jste provedli výše uvedené příkazy, měli byste vidět následující:

UNO
ONE
TWO

Vyzkoušejte následující příkaz a vysvětlete, co se stane (a proč), když spustíte

tac one.txt two.txt >two.txt
Answer.

Přesměrování vstupu

Podobně shell nabízí < pro přesměrování stdin. Pak místo čtení vstupu zadaného uživatelem na klávesnici program čte vstup ze souboru.

Pythonovské programy, které používají input() moc dobře s přesměrovaným vstup nefungují. Prakticky je input() vhodný pouze pro interaktivní programy. Spíše asi budete chtít použít sys.stdin.readline() nebo for line in sys.stdin.

Když je vstup přesměrován, nemusíme zadávat <Ctrl>-D, abychom uzavřeli vstup, protože vstup se uzavře automaticky po dosažení konce souboru.

Standardní vstup a výstup: zkontrolujte si, že rozumíte základům

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

Filtry

Mnoho příkazů v Linuxu funguje jako filtry. Čtou svůj vstup ze stdin a (modifikovaný) výstup zapisují na stdout.

Jeden takový příklad je cut, příkaz, který vytiskne jenom určité sloupce vstupu a ostatní zahodí. Například když spustíme cut -d : -f 1 a standardní vstup bude /etc/passwd, dostaneme seznam účtů (uživatelských jmen) na aktuálním stroji.

Zkuste vysvětlit rozdíl mezi těmito voláními:

cut -d : -f 1 </etc/passwd
cut -d : -f 1 /etc/passwd

Filtry se často chovají přesně takhle: pokud nespecifikujete jméno souboru, filtr pracuje se standardním vstupem. Jestliže specifikujete soubor jako argument, bude filtr pracovat s ním.

K předchozí otázce: rozdíl je v tom, že v prvním případě (s přesměrováním vstupu) otevře /etc/passwd shell a otevřený soubor (nějak) předá programu cut, který spustí. Pokud se soubor nepodaří otevřít, selhání ohlásí shell a cut se nejspíš ani nespustí. Ve druhém případě otevírá /etc/passwd sám běžící cut, který dostane jméno souboru jako argument (tj. cut zavolá open a obslouží případné chyby při otevírání souboru sám).

Pokračování průběžného příkladu

S těmito znalostmi můžeme konečně vyřešit první část našeho příkladu. Připomeňme si, že máme soubory, které zaznamenávají provoz každého dne, a chceme najít adresy URL, které se ve všech souborech dohromady vyskytují nejčastěji.

To znamená, že musíme spojit všechny soubory dohromady, zachovat pouze adresu URL a najít tři nejčastěji se vyskytující řádky.

A to už můžeme udělat. Připomeňme, že cat lze použít ke spojování souborů a cut k zachování pouze některých sloupců. Nalezení nejčastěji se vyskytující adresy URL uděláme už za chvíli.

Tak, co třeba takhle?

#!/bin/bash

cat logs/20[0-9][0-9]-[01][0-9]-[0-3][0-9].csv >_logs_merged.csv
cut -d , -f 5 <_logs_merged.csv

Použili jsme poměrně explicitní wildcard, abychom zajistili, že nebudeme vypisovat náhodné CSV, i když cat logs/*.csv by mohlo fungovat stejně dobře.

Uvědomte si, kolik času by zabralo napsat toto v Pythonu.

Skript má jednu velkou chybu (brzy ji vyřešíme, ale přesto je třeba se o ní zmínit).

Skript zapisuje do souboru s názvem _logs_merged.csv. Názvu souboru jsme předřadili podtržítko, abychom ho označili jako poněkud speciální, ale přesto: co kdyby uživatel takový soubor vytvořil ručně?

Tento soubor bychom bez dalšího ptaní přepsali. A bez možnosti obnovy.

Tohle už ve svých skriptech nikdy nedělejte.

Můžete se také setkat s variantou, kdy je cut voláno jako cut -d, -f3. Většina programů je dostatečně chytrá na to, aby rozpoznala obě varianty, ale je důležité si uvědomit, že s tím si musí poradit každý program sám.

To znamená, že program musí být schopen pracovat s sys.argv[1] == '-d,' a s (sys.argv[1] == '-d') a (sys.argv[2] == ',').

Roury čili pipes (skládání proudových dat)

Konečně se přesuneme do oblasti, ve které Linux vyniká: ke skládání programů. V podstatě celá myšlenka operačních systémů rodiny Unix spočívá v tom, že umožňují snadné skládání různých malých prográmků dohromady.

Většinou se jedná o programy, které se skládají dohromady jako filtry pracujícími s textovými vstupy. Tyto programy nepředpokládají žádný specifický formát textu a jsou velmi obecné. Pokud je vstup více strukturovaný, například XML nebo JSON, jsou potřeba speciální nástroje (které jsou nicméně součástí repozitářů softwaru pro Linux).

Výhodou je, že skládání programů je velmi snadné a lze je také velmi snadno skládat postupně (tj. přidávat další filtr, až když výstup z předchozích vypadá rozumně). Takové inkrementální skládání je obtížnější v běžných jazycích, kde vypisování dat vyžaduje další příkazy (zde se vypisují data na stdout bez práce navíc).

Nevýhodou je, že složité kompozice mohou být obtížně čitelné. Je na vývojáři, aby se rozhodl, kdy je čas přejít na lepší jazyk a zpracovávat data v něm. Typická dělba práce spočívá v tom, že se k předzpracování dat používají shellové skripty: ty jsou nejlepší, když potřebujete spojit data z více souborů (např. stovky různých logů apod.) nebo když je třeba data převést do rozumného formátu (např. nestrukturované logy z webového serveru do CSV načitatelného do vašeho oblíbeného tabulkového procesoru nebo jazyka R). Výpočet statistik a podobné úlohy je pak nejlepší přenechat specializovaným nástrojům.

Netřeba dodávat, že Linux nabízí spoustu nástrojů pro statistické výpočty nebo nástroje pro kreslení grafů, které lze ovládat pomocí CLI. Zvládnutí těchto nástrojů je bohužel mimo téma tohoto kurzu.

Vraťme se znovu k našemu průběžnému příkladu.

Již jsme se zmínili, že použitý dočasného souboru je špatně, protože jsme mohli přepsat cizí data.

Vyžaduje však také místo na disku pro další kopii (pravděpodobně obrovského objemu) dat.

Trochu nenápadnější, ale mnohem nebezpečnější problém spočívá v tom, že cesta k dočasnému souboru je pevně daná. Představte si, co se stane, když skript spustíte ve dvou terminálech současně. Nenechte se zmást pocitem, že skript je tak krátký, že pravděpodobnost souběžného spuštění je zanedbatelná. Je to past, která jen čeká na sklapnutí. O správném použití mktemp(1) si povíme později, ale v tomto příkladu není dočasný soubor vlastně vůbec potřeba.

Učili jsme se o skladbě programů, že? A můžeme ji použít i zde.

cat logs/20[0-9][0-9]-[01][0-9]-[0-3][0-9].csv | cut -d , -f 5

Symbol | znamená rouru (česky též pajpu čili pipe), který spojuje standardní výstup cat se standardním vstupem cut. Roura předává data mezi oběma procesy, aniž by je vůbec zapisovala na disk. (Přesně vzato jsou data předávána pomocí paměťových bufferů, ale to je technický detail.)

Výsledek je stejný, ale vyhnuli jsme se nástrahám používání dočasných souborů a výsledek je ve skutečnosti ještě čitelnější.

Pro případy, kdy první příkaz čte také ze standardního vstupu, je k dispozici jiná syntaxe. Například následující vypíše setříděný seznam místních uživatelských účtů (uživatelských jmen).

cut -d : -f 1 </etc/passwd | sort

Můžeme dokonce přesunout první < před cut, aby bylo možné skript přečíst zleva doprava jako “vezmi /etc/passwd, vezmi první sloupec a pak jej seřaď”:

</etc/passwd cut -d : -f 1 | sort

Rodina unixových systémů je v podstatě postavena na možnosti vytvářet pipelines, které řetězí posloupnost programů pomocí rour. Každý program v rouře označuje typ transformace. Tyto transformace se skládají dohromady a vytvářejí tím konečný výsledek.

Další doplnění našeho průběžného příkladu

Nejprve jsme chtěli vytisknout tři nejnavštěvovanější URL adresy.

Pomocí výše uvedené roury můžeme vypsat všechny adresy URL v jednom seznamu.

Pro zjištění nejčastěji navštěvovaných řádků použijeme typický trik, kdy nejprve seřadíme řádky podle abecedy a poté pomocí programu uniq s -c spočítáme unikátní řádky (takže v podstatě spočítáme, kolikrát byla která URL navštívena). Tento výstup pak seřadíme podle čísel a vypíšeme první 3 řádky.

Náš program se tedy bude vyvíjet takto (řádky začínající # jsou samozřejmě komentáře).

# Získáme všechny URL
cat logs/20[0-9][0-9]-[01][0-9]-[0-3][0-9].csv | cut -d , -f 5

# Zkrátíme si název souboru (jen kvůli ušetření místa)
cat logs/*.csv | cut -d , -f 5

# Setřídíme URL a dostaneme je tak k sobě
cat logs/*.csv | cut -d , -f 5 | sort

# Spočítáme unikátní položky (uniq je sám nesetřídi)
cat logs/*.csv | cut -d , -f 5 | sort | uniq -c

# Setřídíme výstup uniq podle čísel
cat logs/*.csv | cut -d , -f 5 | sort | uniq -c | sort -n

# A vytiskneme jen poslední řádky
cat logs/*.csv | cut -d , -f 5 | sort | uniq -c | sort -n | tail -n 3

Nebojte se. Postupovali jsme po malých krůčcích na každém řádku. Spouštějte jednotlivé příkazy sami a sledujte, jak se výstup mění.

Cvičení

Vypište celkové množství přenesených bajtů z logů z našeho příkladu (tj. poslední část úlohy).

Nápověda: budete potřebovat cat, cut, paste a bc.

První část by měla být snadná: zajímá nás pouze poslední sloupec.

cat logs/*.csv | cut -d , -f 4

Pro sčítání řádků čísel použijeme paste, který umí sloučit řádky z více souborů nebo spojit řádky do jednoho souboru. Dáme mu oddělovač + a vytvoříme tak obrovský výraz VELIKOST1+VELIKOST2+VELIKOST3+....

cat logs/*.csv | cut -d , -f 4 | paste -s -d +

Nakonec to pomocí bc sečteme.

cat logs/*.csv | cut -d , -f 4 | paste -s -d + | bc

bc je poměrně výkonný kalkulátor, který lze používat i interaktivně (připomeňme, že <Ctrl>-D ukončí zadávání v interaktivním režimu).

Další příklady jsou uvedeny na konci tohoto cvičení.

Rychlá kontrola znalosti filtrů

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

Psaní vlastních filtrů

Dokončeme další část průběžného příkladu. Chceme vypočítat návštěvnost (ve smyslu množství přenesených dat) pro každý den a vypsat dny s největší návštěvností.

Vzhledem k tomu, jak jsme zatím vše poskládali, nám chybí pouze prostřední část roury. Součet velikostí pro jednotlivé dny.

Pro tento účel neexistuje žádné hotové řešení (pokročilí uživatelé mohou zvážit instalaci termsql), ale my si vytvoříme vlastní v Pythonu a zapojíme ho do naší pipeline.

Budeme se snažit, aby byl jednoduchý, ale zároveň dostatečně univerzální.

Připomeňme si, že chceme seskupit provoz podle datumů, a proto by náš program měl být schopen provést následující transformaci.

# Vstup
day1 1
day1 2
day2 4
day1 3
day2 1
# Výstup
day1 6
day2 5

Zde je naše verze programu. Všimněte si, že jsme (prozatím) ignorovali ošetření chyb, ale umožnili jsme, aby program mohl být použit jako filtr uprostřed pipeline (tj. číst ze stdin, když nejsou uvedeny žádné argumenty), ale také snadno použitelný pro více souborů.

Ve vlastních filtrech byste měli tento přístup také dodržovat: množství zdrojového kódu, které je třeba napsat, je zanedbatelné, ale uživateli poskytuje flexibilitu při používání.

#!/usr/bin/env python3

import sys

def sum_file(inp, results):
    for line in inp:
        (key, number) = line.split(maxsplit=1)
        results[key] = results.get(key, 0) + int(number)

def main():
    sums = {}
    if len(sys.argv) == 1:
        sum_file(sys.stdin, sums)
    else:
        for filename in sys.argv[1:]:
            with open(filename, "r") as inp:
                sum_file(inp, sums)
    for key, sum in sums.items():
        print(f"{key} {sum}")

if __name__ == "__main__":
    main()

S takovým programem můžeme náš skript webové statistiky rozšířit následujícím způsobem.

cat logs/*.csv | cut -d , -f 1,4 | tr ',' ' ' | ./group_sum.py

Pomocí man zjistíte, co dělá tr.

Sami rozšiřte řešení tak, aby se vytiskly pouze 3 nejlepší dny (sort může řadit řádky i pomocí jiných sloupečků než přes celý řádek). Answer.

Standardní chybový výstup

Zatímco výstup programu chceme často přesměrovat někam jinam, chyby při běhu programu bychom rádi viděli na obrazovce.

Dejme tomu, že existují soubory one.txt a two.txt, ale nonexistent.txt v aktuálním adresáři není. Spustíme následující příkaz.

Ne, nepředstavujte si to. Vytvořte soubory one.txt a two.txt, které budou obsahovat slova ONE a two.txt TWO sami v příkazovém řádku. Hint. Answer.

cat one.txt nonexistent.txt two.txt >merged.txt

cat celkem pochopitelně ohlásí chybu - neexistující soubor přečíst nejde. Kdyby se ale tahle chybová hláška tiskla na stdout, přesměrovala by se do merged.txt společně s výstupem. To by nebylo úplně praktické.

A proto má každý Linuxový program ještě jeden výstup, standardní chybový výstup, kterému se říká stderr (ze standard error [output]). Stderr je obvykle inicializovaný stejně jako stdout, ale logicky je odlišný. Když přesměrujeme stdout pomocí >, stderr to neovlivní.

V Pythonu je stderr dostupný jako sys.stderr. Opět se chová jako otevřený soubor.

Naši implementaci můžeme rozšířit tak, aby zpracovávala chyby I/O:

try:
    with open(filename, "r") as inp:
        sum_file(inp, sums)
except IOError as e:
    print(f"Error reading file {filename}: {e}", file=sys.stderr)

Pohled pod kapotu (aneb o deskriptorech souborů)

Následující text poskytuje přehled souborových deskriptorů, což je abstrakce používaná operačním systémem a aplikacemi při práci s otevřenými soubory. Pochopení tohoto konceptu není pro tento kurz nezbytné, ale jedná se o obecný princip, který je (do určité míry) přítomen ve většině operačních systémů a aplikací (nebo programovacích jazyků).

S každým otevřeným souborem je asociovaný takzvaný file descriptor. File descriptory se používají jako identifikátory otevřených souborů při komunikaci s operačním systémem (říkali jsme, že vlastní souborové operace vykonává operační systém). File descriptor je celé číslo, které slouží jako index do tabulky aktuálně otevřených souborů, kterou si operační systém udržuje pro každý proces (proces je pro nás běžící instance programu).

Toto číslo – file deskriptor – je pak předán systémovýmu volání (syscall), které s ním pracuje. Například syscall write dostane dva argumenty: file descriptor a buffer s daty, která má operační systém zapsat (v příkladech je teď budeme pro jednoduchost předávat přímo jako string). Takže když váš program zavolá print("Message", file=some_file), ve skutečnosti se “někde vespod” provede volání write(3, "Message\n"), kde 3 je file descriptor asociovaný s otevřeným souborem some_file.

Tohle je trochu technické, ale pomůže vám to pochopit, proč přesměrování standardního chybového výstupu vypadá tak, jak vypadá, a proč většina operací se soubory vyžaduje, aby byl příslušný soubor nejprve otevřen (tedy proč write_to_file(filename, contents) není elementární operace).

V unixovém prostředí jsou file descriptory 0, 1 a 2 po řadě přiřazeny standardnímu vstupu, výstupu a chybovému výstupu. Proto print("Message") v Pythonu nakonec zavolá write(1, "Message\n") a volání print("Error", file=sys.stderr) volá write(2, "Error\n").

Při spuštění nového procesu získá tyto tři deskriptory souborů od svého volajícího (např. shellu). Ve výchozím nastavení ukazují na terminál, ale volající je může jednoduše otevřít tak, aby ukazovaly na jiný soubor. Takto také funguje přesměrování.

Skutečnost, že stdout a stderr jsou logicky odlišné proudy (soubory), také vysvětluje slovo pravděpodobně v jednom z výše uvedených příkladů. Přestože oba končí ve stejném fyzickém zařízení (terminálu), mohou používat odlišnou konfiguraci: standardní výstup je typicky bufferovaný, tj. výstup vaší aplikace jde na obrazovku až když je ho dostatek, zatímco standardní chybový výstup bufferovaný není - je vypisován okamžitě. Důvod je pravděpodobně zřejmý – chybová hlášení by měla být viditelná co nejdříve, zatímco normální výstup může být kvůli zvýšení výkonu přeci jen o chvíli odložen.

Bufferování se obecně může chovat složitěji. Stačí ale myslet na to, že zatímco standardní chybový výstup obvykle vidíme hned, standardní výstup může být zpožděný.

Pokročilé přesměrování vstupu/výstupu

Ujistěte se, že máte k dispozici skript group_sum.py.

Připravte si soubory one.txt a two.txt:

echo ONE 1 > one.txt
echo ONE 1 > two.txt
echo TWO 2 >> two.txt

Nyní proveďte následující příkazy.

./group_sum.py <one.txt
./group_sum.py one.txt
./group_sum.py one.txt two.txt
./group_sum.py one.txt <two.txt

Chovalo se to podle vašich očekávání?

Sledujte, jakými cestami (tj. přes které řádky) program prošel při výše uvedených spuštěních.

Přesměrování standardního chybového výstupu

Chcete-li přesměrovat standardní chybový výstup, můžete opět použít >, ale tentokrát s číslem 2 (které označuje deskriptor souboru stderr).

Proto lze náš příklad cat transformovat do následující podoby, kdy err.txt bude obsahovat chybovou zprávu a na obrazovku se nic nevypíše.

cat one.txt nonexistent.txt two.txt >merged.txt 2>err.txt

Přesměrování uvnitř a dovnitř skriptu

Uvažujme následující malý skript (first-column.sh) který vezme a setřídí první sloupec (z dat oddělených dvojtečkou, tj. například soubor /etc/passwd).

#!/bin/bash

cut -d : -f 1 | sort

Potom může uživatel skript použít takto a standardní vstup cutu bude vždy správně propojen se standardním vstupem shellu nebo skrz rouru.

cat /etc/passwd | ./first-column.sh
./first-column.sh </etc/passwd
head /etc/passwd | ./first-column.sh | tail -n 3

Výše uvedený příklad je poněkud umělý, ale dobře demonstruje, že stdin je přirozeně dostupný i uvnitř skriptů bez ohledu na přesměrování z vnějšku.

Obecné přesměrování

Shell nám umožňuje přesměrovat výstupy zcela libovolně pomocí čísel deskriptorů souborů před a za znaménkem větší než.

Například >&2 určuje, že standardní výstup bude přesměrován na standardní chybový výstup. Může to znít divně, ale vezměme si následující miniskript.

Zde se wget používá ke stažení souboru ze zadané adresy URL.

echo "Downloading tarball for lab 02..." >&2
wget https://d3s.mff.cuni.cz/f/teaching/nswi177/202122/labs/nswi177-lab02.tar.gz 2>/dev/null

Ve skutečnosti chceme skrýt zprávy o průběhu wget a místo nich vypsat naše zprávy.

Berte to jako ilustraci konceptu, protože wget lze ztišit i pomocí argumentů příkazového řádku (--quiet).

Někdy chceme přesměrovat stdout a stderr do jednoho souboru. V těchto situacích by prosté >output.txt 2>output.txt nefungovalo a musíme použít >output.txt 2>&1 nebo &>output.txt (pro přesměrování obou najednou). Můžeme použít také 2>&1 >output.txt? Vyzkoušejte si to sami! Hint.

Důležité speciální soubory

Již jsme se zmínili, že prakticky vše v Linuxu je soubor. Mnoho speciálních souborů reprezentujících zařízení se nachází v podadresáři /dev/.

Některé z nich jsou velmi užitečné pro přesměrování výstupu.

Spusťte cat one.txt a přesměrujte výstup na /dev/full a poté na /dev/null. Co se stalo?

Zejména soubor /dev/null je velmi užitečný, protože jej lze použít v každé situaci, kdy nás výstup programu nezajímá.

U mnoha programů můžete explicitně určit použití stdin pomocí - (pomlčka) jako názvu vstupního souboru.

Další možností je explicitně použít /dev/stdin: s tímto názvem můžeme zprovoznit příklad s group_sum.py:

./group_sum.py /dev/stdin one.txt <two.txt

Pak Python otevře soubor /dev/stdin jako soubor a operační systém (spolu s shellem) jej skutečně spojí se souborem two.txt.

/dev/stdout lze použít pokud chceme explicitně zadat standardní výstup (to je užitečné hlavně pro programy pocházející z jiných prostředí, kde není kladen takový důraz na použití stdout).

Návratová hodnota programu (exit code)

Doposud naše programy chybu ohlašovaly tak, že zapsaly chybovou zprávu na standardní chybový výstup. To je celkem užitečné pro interaktivní programy, protože uživatel chce vědět, co se pokazilo.

Nicméně pro neinteraktivní použití by bylo nepohodlné rozpoznávat chyby tak, že bychom hledali chybové hlášky na standardním chybovém výstupu. Chybové hlášky se mění, mohou být lokalizované atd. Existuje proto jiný způsob, jak můžeme poznat, zda běh programu skončil chybou či nikoli.

Úspěch či neúspěch poznáme podle exit code. Exit code je celé číslo a na rozdíl od jiných programovacích jazyků indikuje 0 úspěch a libovolný nenulový kód indikuje chybu.

Uhodnete, proč autoři zvolili nulu pro indikaci úspěchu (zatímco v jiných jazycích je logická hodnota nuly false), zatímco nenulové kódy (jejichž logická hodnota je obvykle true) byly použity pro chyby? Nápověda: kolika způsoby může program uspět?

Není-li řečeno jinak, když váš program úspěšně doběhne (například v Pythonu doběhne main a nedojde k vyhození výjimky), exit code by měl být nula.

Chcete-li změnit toho chování, můžete program ukončit s jiným chybovým kódem jako argumentem funkce exit.V Pythonu je to sys.exit`.

U programů v jazyce C vrací funkce main ve skutečnosti hodnotu int, jejíž hodnotou je návratový kód programu. Používejte ji správně.

Plná signatura je vlastně int main(int argc, char *argv[]), takže můžete přistupovat k volbám příkazového řádku jako k argumentům funkce (většina prostředí vám ve skutečnosti umožní použít prosté void main(void), ale nedoporučuje se to).

Následující příklad je modifikace výše uvedeného souboru group_sum.py, tentokrát se správným zpracováním exit kódu.

def main():
    sums = {}
    exit_code = 0
    if len(sys.argv) == 1:
        sum_file(sys.stdin, sums)
    else:
        for filename in sys.argv[1:]:
            try:
                with open(filename, "r") as inp:
                    sum_file(inp, sums)
            except IOError as e:
                print(f"Error reading file {filename}: {e}", file=sys.stderr)
                exit_code = 1
    for key, sum in sums.items():
        print(f"{key} {sum}")
    sys.exit(exit_code)

Později uvidíme, že různé control-flow konstrukce v shellu (podmínky a smyčky) se řídí právě tím, jaký byl exit code jednotlivých příkazů.

Rychlé selhávání

Dosud jsme očekávali, že naše shellové skripty nikdy neselžou. Na žádné chyby jsme je ani nepřipravovali.

Časem se podíváme, jak lze exit kódy testovat a používat k lepšímu řízení našich shellových skriptů, ale zatím chceme jen zastavit, kdykoli dojde k nějaké chybě.

To je vlastně docela rozumné chování: obvykle chcete, aby se celý program ukončil, pokud dojde k neočekávanému selhání (místo aby pokračoval s nekonzistentními daty). Podobně jako nezachycená výjimka v Pythonu.

Chcete-li povolit ukončení při chybě, musíte zavolat set -e. V případě neúspěchu shell ukončí provádění skriptu a skončí se stejným exit kódem jako neúspěšný příkaz.

Kromě toho obvykle chcete skript ukončit, pokud je použita neinicializovaná proměnná: to umožňuje set -u. O proměnných si povíme později, ale -e a -u se obvykle nastavují společně.

A ještě jedno upozornění týkající se roury a úspěšnosti příkazů: úspěšnost roury je určena jejím posledním příkazem. Příkaz sort /nonexistent | head je tedy úspěšný příkaz. Chcete-li, aby neúspěch některého příkazu způsobil neúspěch (celé) pipeline, musíte ve skriptu (nebo shellu) před pipeline spustit příkaz set -o pipefail.

Obvykle tedy chcete skript začít následující trojicí:

set -o pipefail
set -e
set -u

Mnoho příkazů umožňuje takto slučovat krátké volby (jako -l nebo -h, které znáte z ls) (všimněte si, že -o pipefail musí být na posledním místě):

set -ueo pipefail

Navykněte si začínat každý váš skript tímto příkazem.

Pipeline GitLabu budou od této chvíle kontrolovat, zda je tento příkaz součástí vašich skriptů.

Úskalí rour (alias SIGPIPE)

set -ueo pipefail může někdy způsobit nežádoucí a zcela neočekávané chování.

Následující skript skončí s těžko vysvětlitelnou chybou, tj. nikdy se nedostaneme k závěrečnému echo. Všimněte si, že závěrečný hexdump je zde pouze proto, abychom zajistili, že nevypíšeme smetí z /dev/urandom přímo na terminál.

#!/bin/bash

set -ueo pipefail

cat /dev/urandom | head -n 1 | hexdump

echo OKAY NOT PRINTED

Přestože vše vypadá v pořádku.

Důvodem je příkaz head. příkaz head má velmi chytrou implementaci, která ukončí činnost po vypsání prvních -n řádků. Rozumné, že? Ale to znamená, že první cat najednou zapisuje do roury, kterou nikdo nečte. Je to jako zápis do souboru, který už byl uzavřen. To generuje výjimku (no, tak trochu) a cat se ukončí s chybou. Kvůli set -o pipefail selže celá pipeline.

Pravdou je, že rozlišit, zda je uzavřená roura platnou situací, kterou je třeba vůbec řešit, nebo zda indikuje problém, je nemožné. Proto se cat ukončí s chybou (někdo přeci jen uzavřel jeho výstup, aniž by mu to dal předtím vědět), a shell tak musí označit celou pipeline za neúspěšnou.

Řešení tohoto problému není vždy snadné a je k dispozici několik možností. Každá z nich má své výhody a nevýhody.

Pokud víte, proč k tomu může dojít, přidáním || true označíte rouru jako v pořádku (o || se však dozvíme později).

Návratové kódy: zkontrolujte si, zda této části rozumíte

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

Přizpůsobení shellu

Již jsme se zmínili, že byste si měli emulátor terminálu přizpůsobit tak, aby se vám pohodlně používal. Koneckonců s ním strávíte minimálně tento semestr a jeho používání by vás mělo bavit.

V tomto cvičení si ukážeme některé další možnosti, jak si zpříjemnit používání shellu.

Aliasy příkazů

Pravděpodobně jste si všimli, že některé příkazy se stejnými argumenty voláte často. Jedním z takových příkladů může být ls -l -h, který vypíše podrobný výpis souborů s použitím velikostí čitelných pro člověka. Nebo třeba ls -F, který k adresářům připojí lomítko. A pravděpodobně také ls --color.

Shell nabízí vytvoření takzvaných aliasů, kde můžete snadno přidávat nové příkazy, aniž byste museli někde vytvářet plnohodnotné skripty.

Zkuste provést následující příkazy, abyste zjistili, jak lze definovat nový příkaz l.

alias l='ls -l -h`
l

Můžeme dokonce přepsat původní příkaz, shell zajistí, aby přepisování nebylo rekurzivní.

alias ls='ls -F --color=auto'

Všimněte si, že tyto dva aliasy společně zajišťují, že l bude zobrazovat názvy souborů barevně.

Kolem znaménka rovná se mezery nepíšeme.

Mezi typické aliasy, které pravděpodobně budete chtít vyzkoušet, patří následující. Pokud si nejste jisti, k čemu alias slouží, použijte manuálovou stránku. Všimněte si, že curl se používá k načtení obsahu z adresy URL a wttr.in je skutečně adresa URL. Mimochodem, tento příkaz vyzkoušejte, i když nemáte v plánu tento alias používat :-).

alias ls='ls -F --color=auto'
alias ll='ls -l'
alias l='ls -l -h'

alias cp='cp -i'
alias mv='mv -i'
alias rm='rm -i'

alias man='man -a'

alias weather='curl wttr.in'

~/.bashrc

Výše uvedené aliasy jsou pěkné, ale pravděpodobně je nechcete definovat při každém spuštění shellu. Většina shellů v Linuxu však má nějaký soubor, který spustí před vstupem do interaktivního režimu. Obvykle se tento soubor nachází přímo ve vašem domovském adresáři a je pojmenován podle shellu a končí na rc (což si můžete zapamatovat jako runtime configuration, čili běhové nastavení).

Pro Bash, který nyní používáme (pokud používáte jiný shell, pravděpodobně již víte, kde najdete jeho konfigurační soubory), se tento soubor nazývá ~/.bashrc.

Již jste jej použili při nastavení EDITORu pro Git, ale můžete tam také přidávat aliasy. V závislosti na vaší distribuci se tam již mohou zobrazovat existující aliasy nebo jiné příkazy.

Přidejte tam aliasy, které se vám líbí, uložte soubor a spusťte nový terminál. Zkontrolujte, zda aliasy fungují.

Soubor .bashrc se chová jako skript shellu a nejste omezeni na to, abyste v něm měli pouze aliasy. Můžete v něm mít prakticky libovolné příkazy, které chcete spouštět v každém terminálu, který spustíte.

Změna promptu ($PS1)

Můžete také upravit vzhled promptu. Výchozí nastavení je obvykle rozumné, ale někteří lidé chtějí vidět více informací. Pokud patříte mezi ně, zde jsou podrobnosti (berte je jako přehled, protože přizpůsobení promptu je téma na celou knihu).

Prompt se mění pomocí proměnné PS1. O proměnných budeme podrobněji hovořit později, nyní se naučíme pouze syntaxi.

Při nastavování proměnné ji můžeme přímo změnit v shellu a ihned sledovat výsledek.

Nyní proveďte následující příkazy.

PS1=''

Prompt je pryč. Nastavili jsme jej na prázdný řetězec.

PS1='Zadejte sve prikazy: '

Tohle je mnohem lepší, že?

A co třeba:

PS1='\w '

Zde nastavíme, aby vypisoval aktuální adresář a mezeru. Speciální sekvence \w bude automaticky nahrazena názvem pracovního adresáře.

Mnoho uživatelů chce vědět, jako který uživatel jsou přihlášeni.

PS1='\u: \w '

Obvyklou tradicí je končit prompt znakem dolaru.

PS1='\u \w\$ '

Pomocí speciální sekvence \[\033[01;32m\] a \[\033[0m\] můžeme změnit i barvu promptu.

PS1='\[\033[01;32m\]\u \w\[\033[0m\]\$ '

Chcete-li jinou barvu, použijte místo 32 jiná čísla. Speciální hodnota 0m přepne zpět na výchozí barvu terminálu.

Je také možné přidat vlastní příkazy, které se mají spustit, nebo dokonce vytvořit víceřádkový prompt.

PS1='$( date ) \u \w\$ '

Zde speciální část $( date ) označuje, že výstup z programu date se stane součástí promptu (o konstrukci $( ) si povíme později, zde ji berte jen jako takovou ochutnávku).

Použití \n nám umožňuje rozdělit prompt na více řádků.

PS1='\n$( date )\n\u \w\$ '

A samozřejmě lze vše kombinovat.

PS1='\n\[\033[01;32m\]$( date )\[\033[0m\]\n\[\033[01;34m\]\u\[\033[0m\] \[\033[01;35m\]\w\[\033[0m\]\$ '

Další příklady

Následující příklady lze vyřešit buď provedením několika příkazů nebo spojením základních příkazů shellu. K nalezení správného programu vám mohou pomoci manuálové stránky. Jako výchozí bod můžete také použít náš manuál.

Všimněte si, že žádné z řešení nevyžaduje nic jiného než použití několika rour. Pro pokročilé uživatele: rozhodně nepotřebujete if nebo while nebo read nebo dokonce používat PERL nebo AWK.

Použijte následující CSV s údaji o tom, jak dlouho trvalo zkopírování obrazu disku USB na flash disky v knihovně. První sloupec představuje zařízení, druhý dobu trvání kopírování.

Ve skutečnosti první sloupec nepřímo představuje také port rozbočovače USB (je to spíše náhoda, ale vyplývá to ze způsobu, jakým jsme kopírování uspořádali). Poznámka na okraj: je zajímavé, že některé porty, které mají být stejné, jsou ve skutečnosti systematicky pomalejší.

Chceme vědět, jaká byla nejdelší doba trvání kopírování: jinými slovy, maximum ze sloupce dva.

Solution.

Vytvořte adresář a a v něm textový soubor --help obsahující Lorem Ipsum. Vypište obsah tohoto souboru a poté jej smažte. Solution.
Vytvořte adresář b a v něm soubory s názvem alpha.txt a *. Poté smažte soubor s názvem * a sledujte, co se stalo se souborem alpha.txt. Solution.
Vypište obsah souboru /etc/passwd seřazený podle řádků. Solution.
Vypište první a třetí sloupec souboru /etc/group. Solution.
Spočítejte řádky souboru /etc/services. Solution.
Vypište poslední dva řádky souborů /etc/passwd a /etc/group pomocí jediného příkazu. Solution.
Připomeňte si soubor disk-speeds-data.csv s údaji o délce kopírování disku. Vypočítejte celkovou dobu trvání všech kopírování dohromady. Solution.

Předpokládejme následující formát souboru.

Alpha     8  4  5  0
Bravo    12  5  3  2
Charlie   1  0 11  4

Na konec každého řádku přidejte hodnotu odpovídající součtu čísel na daném řádku. Není nutné zachovávat původní zarovnání (tj. klidně si spojte mezery do jedné).Hint. Solution.

Vypište obsah souborů /etc/passwd a /etc/group oddělený textem Ha ha ha (tj. obsah /etc/passwd, řádek s Ha ha ha a obsah /etc/group). Solution.

Vytiskněte výrobce vašeho CPU. Jako výchozí bod použijte soubor /proc/cpuinfo.

Solution.

Úlohy před cvičením (deadline: začátek vašeho cvičení, týden 6. března - 10. března)

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

Omlouváme se, ale automatické testy ještě nejsou připraveny. Nahrajeme je co nejdříve. Testy jsou dostupné.
Toto cvičení je o rourách. Shellové úlohy tedy musí být vyřešeny pomocí rour, nikoliv pomocí cyklu (i pokud je v shellu už znáte) nebo řešením v jiném programovacím jazyce.

04/line_count.sh (30 bodů, skupina shell)

Spočtěte celkový počet řádků všech textových souborů (tj. *.txt) v aktuálním adresáři. Skript vypíše pouze jedno číslo.

Můžete předpokládat, že vždy alespoň jeden takový soubor bude přítomen.

04/users.sh (40 bodů, skupina admin)

Vytiskněte skutečná jména uživatelů, která obsahují system kdekoli v jejich záznamu (tj. slovo system se objeví kdekoli na řádku).

Seznam uživatelů je uložen buď v /etc/passwd, nebo pomocí getent passwd. Váš skript bude předpokládat, že seznam uživatelů bude přicházet přes standardní vstup.

Proto jej otestujte pomocí getent passwd | 04/users.sh.

04/fastest.sh (30 bodů, skupina shell)

Předpokládejme následující vstupní formát (doby trvání jsou celá čísla) obsahující doby běhu programu spolu s jejich autory.

name1,duration_in_seconds_1
name2,duration_in_seconds_2

Napište autora nejrychlejšího řešení (můžete předpokládat, že doby trvání jsou různé).

Úlohy po cvičení (deadline: 26. března)

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

Omlouváme se, ale automatické testy ještě nejsou připraveny. Nahrajeme je co nejdříve. Testy jsou dostupné.
Toto cvičení je o rourách. Shellové úlohy tedy musí být vyřešeny pomocí rour, nikoliv pomocí cyklu (i pokud je v shellu už znáte) nebo řešením v jiném programovacím jazyce.

04/row_sum.sh (50 bodů, skupina shell)

Předpokládejme, že mám matici zapsané v takovémhle “krásném” formátu. Můžete se spolehnout, že formát je pevně daný (s ohledem na mezery, maximálně trojciferné číslo a symbol pipe) ale může se lišit počet řádků i sloupců.

Napište skript, který sečte čísla v každém řádku.

Počítáme, že pro následující matici dostaneme tento výstup.

| 106 179 |
| 188  50 |
|   5 125 |
285
238
130

Skript bude vstup číst na stdinu, počet sloupců a řádků není nijak omezen (kromě celkového formátu).

04/day_of_week.py (50 bodů, skupina devel)

Napište filtr v Pythonu, který převede datum na den v týdnu.

Program převede data pouze v prvním sloupci (s použitím bílých znaků pro rozdělení do sloupečků), neplatná data budou ignorována (a řádek bude zachován v nezměněné podobě). Zbytek sloupce bude zkopírován do výstupu.

2023-02-20 Rest of the line
Some other line
2023-02-21 Line  contents
Monday Rest of the line
Some other line
Tuesday Line  contents

Program musí být možné spustit jako:

04/day_of_week.py <input.txt
04/day_of_week.py input.txt
cat one.txt two.txt | 04/day_of_week.py

Pokud se soubor nepodaří otevřít, program vypíše chybovou zprávu na stderr (přesné znění je definováno v testech) a ukončí se s kódem 1.

Můžete očekávat, že program nebude spouštěn jako 04/day_of_week.py one.txt two.txt.

Předpokládáme, že budete používat funkce z modulu datetime.

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, co je standardní výstup a vstup

  • vysvětlit, proč přesměrování standardního vstupu/výstupu není (přímo) viditelné uvnitř programu

  • vysvětlit, proč existují dva druhy výstupu (výstupních proudů): stdout a stderr

  • vysvětlit, jak se liší cat foo.txt a cat <foo.txt

  • vysvětlit, jak může být více programů používajících standardní vstup/výstup složeno (propojeno) dohromady

  • vysvětlit co je návratový kód programu (exit code)

  • vysvětlit rozdíly a typické využití pro pět hlavních rozhraní, které může využít CLI program: argumenty, stdin, stdout, stderr a návratová hodnota (exit code)

  • volitelné: vysvětlit, co je to deskriptor souboru (z pohledu aplikace, nikoliv OS/kernelu)

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 …

  • přesměrovat standardní (chybový) výstup a vstup CLI programů v shellu

  • změnit návratovou hodnotu (exit code) pro Pythoní skripty

  • používat speciální soubor /dev/null

  • používat standardní vstup a výstup v Pythonu

  • použít rouru (pipe) | pro řetězení více programů dohromady

  • používat základní filtry: cut, sort, …

  • použít grep -F pro filtrování řádků odpovídajících zadanému vzoru

  • volitelné: upravit si chování shellu pomocí aliasů

  • volitelné: upravit si konfiguraci shellu pomocí .bashrc a .profile skriptů

  • volitelné: upravit si vzhled promptu pomocí proměnné PS1

Seznam změn na této stránce

  • 2023-02-25: Úloha 04/users.sh přesunuta do skupiny admin.

  • 2023-03-03: Zdůraznění jak je stdin předáván dovnitř skriptu.