Nauč se Python > Kurzy > Linuxová administrace > Bash II > Roury a filtry

Roury a filtry

Teď, když znáš základní příkazy, si ukážeme nejmocnější vlastnost shellu: možnost příkazy jednoduše spojovat dohromady. Začni opět v adresáři molecules, kde je několik souborů s popisem molekul:

$ cd ~/Dokumenty/data-shell/molecules
$ ls -F
cubane.pdb  ethane.pdb  methane.pdb  octane.pdb  pentane.pdb  propane.pdb

Pomocí wc -l si vypiš, kolik mají tyto soubory řádků:

$ wc -l *.pdb
  20 cubane.pdb
  12 ethane.pdb
   9 methane.pdb
  30 octane.pdb
  21 pentane.pdb
  15 propane.pdb
 107 celkem

Velikost všech souborů by vypsal i wc -l *, ale psát *.pdb místo * se začne hodit hned za chvíli, když začneš tvořit nové soubory.

Který z těchto souborů je největší? A který je nejmenší? Tohle jsou docela jednoduché otázky, ale představ si, že bys těch souborů měla 6000. To už by se hodilo zeptat počítače spíš než hledat očima.

Přesměrování výstupu

První věc, kterou uděláme, je přesměrování výstupu. Pomocí > pusť příkaz wc tak, aby místo na obrazovku zapisoval do souboru:

$ wc -l *.pdb > delky.txt

Na obrazovku se nic nevypíše, ale vytvoří se soubor delky.txt:

$ ls delky.txt
delky.txt

A v tom souboru je všechno, co by příkaz wc normálně vypsal do terminálu:

$ cat delky.txt
  20 cubane.pdb
  12 ethane.pdb
   9 methane.pdb
  30 octane.pdb
  21 pentane.pdb
  15 propane.pdb
 107 celkem

Jak to funguje?

Každý příkaz v Bashi (a obecně každý proces — běžící program na počítači) má k dispozici takzvaný standardní výstup (angl. standard output, stdout): místo, kam může vypisovat výsledky. Bash předtím, než spustí nějaký program, nastaví tenhle standardní výstup buď do terminálu nebo do souboru. Programy jako cat, wc nebo tail se pak nestarají o to, kde ty výsledky skončí. Prostě je vypíšou na předem určené místo.

Diagram příkazu `wc -l *.pdb`

Diagram příkazu `wc -l *.pdb` přesměrovaného do souboru

Pomocí > říkáš Bashi, aby výstup programu přesměroval do souboru. Když soubor daného jména neexistuje, Bash ho vytvoří. Pokud existuje, Bash jeho obsah ještě před spuštěním příkazu smaže a nechá příkaz, ať do něj zapíše něco nového. Takže postupuj opatrně: jak už víš, v příkazové řádce není odpadkový koš. Ani tlačítko Zpět.

Přesměrováním přepíšeš existující soubor

A ještě jednou v červeném rámečku: pokud soubor za > existuje, smaže se. Na kurzu to nevadí – virtuální počítač máš na hraní – ale „v reálném světě“ si dělej zálohy.

Hadí odbočka

Program python má přepínač -c, který umožňuje zadat pythonní příkaz přímo jako argument příkazové řádky. I výstup této operace můžeš přesměrovat do souboru a dál s ním pracovat.

Kód v Pythonu často obsahuje speciální znaky (mezery, závorky, atd.), takže je ho často potřeba uzavřít do jednoduchých uvozovek:

$ python -c 'print(2 + 2)' > ctyri.txt
$ cat ctyri.txt
4

Řazení

Seznam se s příkazem sort (angl. seřaď), který seřadí řádky daného souboru.

Soubor delky.txt teď vypadá takhle:

$ cat delky.txt
  20 cubane.pdb
  12 ethane.pdb
   9 methane.pdb
  30 octane.pdb
  21 pentane.pdb
  15 propane.pdb
 107 celkem

A příkaz sort ho seřadí následujícím způsobem:

$ sort delky.txt
 107 celkem
  12 ethane.pdb
  15 propane.pdb
  20 cubane.pdb
  21 pentane.pdb
  30 octane.pdb
   9 methane.pdb

To není úplně ono – sort totiž řadí podle abecedy, nikoli číselně; 102 je tedy před 12 podobně jako BAC je v abecedě před BC.

Ale jak už to bývá, tohle chování se dá změnit pomocí přepínače – v tomto případě přepínače -n (--numeric-sort, číselné řazení):

$ sort -n delky.txt
   9 methane.pdb
  12 ethane.pdb
  15 propane.pdb
  20 cubane.pdb
  21 pentane.pdb
  30 octane.pdb
 107 celkem

Bez přepínače -n řadí sort podle zvoleného jazyka, např. česky nebo anglicky. Jazyk se dá zvolit pomocí nastavení LC_ALL před příkazem:

LC_ALL=czech sort soubor.txt    # řadí česky
LC_ALL=en_US sort soubor.txt    # řadí anglicky (americká angličtina)
LC_ALL=C sort soubor.txt        # řadí byty (tak jako Python – a jazyk C)

Pozor na velikost písmen a na to, že před a za rovnítkem není mezera. Všechny nainstalované jazyky ti vypíše příkaz locale -a.

Ale teď zpátky k příběhu – soubor delky.txt jsme seřadili podle čísel na začátku.

Nejmenší molekula

Která molekula tedy obsahuje nejmíň atomů?

Když umíš jména souborů seřazené podle počtu řádků vypsat, stejně tak je můžeš uložit do souboru. A ten soubor potom můžeš dál zpracovávat. Nejkratší soubor bude v prvním řádku; prvních n řádků souboru vypíšeš pomocí head:

$ sort -n delky.txt > delky-serazene.txt
$ head -n1 delky-serazene.txt
   9 methane.pdb

Celý postup zjištění nejkratší molekuly tedy je:

$ wc -l *.pdb > delky.txt
$ sort -n delky.txt > delky-serazene.txt
$ head -n1 delky-serazene.txt
   9 methane.pdb

Neboli česky:

  • Počty řádků a jména souborů zapiš do delky.txt.
  • Soubor delky.txt seřaď (číselně) a zapiš do souboru delky-serazene.txt.
  • Ze souboru delky-serazene.txt vypiš první řádek.

Tahle sekvence příkazů bude fungovat i kdyby souborů s molekulami bylo 6000. Můžeš si je zapsat do souboru a použít příště, až zase budeš hledat nejmenší molekulu!

Jde to zjednodušit ještě dál, ale než si ukážeme jak na to, podívejme se na přesměrování vstupu.

Přesměrování vstupu

Tak jako jde přesměrovat výstup příkazu pomocí > – tedy to, co by se vypsalo na obrazovku, zapsat do souboru, jde přesměrovat i vstup – tedy to, co bys napsala na klávesnici, načíst ze souboru.

Už víš, že když příkaz jako cat pustíš bez jména souboru, načítají informace „z klávesnice“:

$ cat
haló haló,
haló haló,
co se stalo?
co se stalo?
^C

Podobně jako u standatního výstupu existuje standardní vstup (angl. standard input, stdin) – místo, odkud program získává textové informace. Normálně to je terminál, tedy „klávesnice“:

Diagram příkazu `cat`

Standartní vstup můžeš přesměrovat tak, aby místo toho, co napíšeš klávesnici, příkaz zpracoval daný soubor.

$ cat < delky.txt
  20 cubane.pdb
  12 ethane.pdb
   9 methane.pdb
  30 octane.pdb
  21 pentane.pdb
  15 propane.pdb
 107 celkem

Diagram příkazu `cat`

Spousta programů standardní vstup nepoužívá:

  • ls, cd nebo mv dostávají všechny potřebné informace jako argumenty, nepotřebují vstup z klávesnice;
  • Příkazy s argumentem jako cat soubor.txt nebo wc soubor.txt čtou daný soubor. Klávesnici ignorují, i když jim je k dispozici:

    Diagram příkazu `cat soubor.txt`

Spousta příkazů ale standardní vstup používá když je zavoláš bez argumentu:

$ sort -n < delky.txt
   9 methane.pdb
  12 ethane.pdb
  15 propane.pdb
  20 cubane.pdb
  21 pentane.pdb
  30 octane.pdb
 107 celkem
$ wc -l < delky.txt
7
$ head -n1 < delky-serazene.txt
   9 methane.pdb

wc bez argumentu se chová tak, jako kdybys obsah delky.txt napsala na klávesnici.

Všimni si rozdílu mezi příkazy:

$ wc -l delky.txt
$ wc -l < delky.txt

Při použití přesměrování příkaz wc nevypíše název souboru. Používá standardní vstup; “neví” že to co čte pochází ze souboru na disku nebo z klávesnice.

Soubor mu v tomto případě otevřel Bash. K otevření je jméno souboru potřeba; k dalšímu čtení ale už ne.

K čemu je to dobré? Zatím, pravda, k ničemu. Příkazy cat, wc i sort umí soubory z disku otevřít samy; nepotřebují na to od Bashe pomoc.

Vstup a výstup ale můžeš přesměrovat i na jiné věci než jen soubory na disku.

Datové potrubí

Najdi na klávesnisi znak |, kterému budu říkat svislítko. Tenhle znak nejspíš nemá oficiální české jméno. Formálně se mu dá říkat svislá čára, často ale uslyšíš pojmenování roura nebo pajpa (z angl. pipe, roura), která vychází z funkce tohoto znaku v Bashi. Tak moc je ta funkce důležitá.

Když svislítkem oddělíš dva příkazy, standardní výstup prvního z nich se přesměruje na standardní vstup toho druhého. Dva příkazy tak spojíš dohromady: první připraví mezivýsledek se kterým ten druhý pak pracuje dál.

Zkus si to na příkladu:

$ sort -n delky.txt | head -n 1
   9 methane.pdb

Diagram příkazů `sort -n delky.txt | head -n 1`

Toto je ekvivalent dvou příkazů, které jsi použila dříve – jen s tím rozdílem, že se pro mezivýsledek nepoužívá soubor, ale text se přesměruje rovnou z jednoho příkazu do druhého.

$ sort -n delky.txt > delky-serazene.txt
$ head -n1 < delky-serazene.txt
   9 methane.pdb

Diagram dvojice příkazů výše

Takovému přesměrování se říká roura (angl. pipe): můžeš si představit, že text “teče” z jednoho příkazu do druhého.

Z rour můžeš sestavovat celá potrubí, kterým budou data proudit mezi jednotlivými příkazy:

$ wc -l *.pdb | sort -n | head -n 1

Diagram příkazů `wc -l *.pdb | sort -n | head -n 1`

Toto spojování stojí za úspěchem Unixových shellů. Místo obrovských programů (např. tabulkový procesor, textový editor, webový prohlížeč) které mají nepřeberné množství různých funkcí se Unixoví programátoři soustředí na jednoduché nástroje (např. cat, wc, head, které bys uměla napsat v Pythonu i ty). Každý z těchto jednoduchých nástrojů dělá jen jednu věc, ale dělá ji dobře. A pomocí rour jde nástroje spojovat k řešení mnohem komplexnějších situací, než na jaké můžou myslet autoři složitých programů.

Každý způsob má samozřejmě svoje místo. Nic tě nenutí používat jen příkazovou řádku, která je mnohem složitější na ovládnutí než „klikátko“ s předpřipravenou nabídkou akcí. Ale jakmile se Bash a malé nástroje naučíš efektivně používat, sama poznáš jak jsou užitečné. Ne všechno co potřebuješ je totiž v “klikátcích” předpřipravené.

Modelu pospojovaných nástrojů se také říká roury a filtry (angl. pipes and filters). Roury už znáš; filtry jsou nástroje jako wc a sort, které nějak transformují standardní vstup na standardní výstup. Většina Unixových nástrojů funguje právě takhle: pokud jim nepředáš argument, čtou ze std. vstupu a výsledky píší na std. výstup.

Stejný vstup a výstup

Není dobrý nápad nastavit výstup na soubor, který v “potrubí” používáš jinde. Například:

$ sort -n delky.txt > delky.txt

Když totiž Bash “potrubí” připravuje, často otevře soubory ještě předtím než spustí příkaz. A protože při přesměrování do souboru se existující soubor vyprázdní, bude sort v příkazu výše číst z už prázdného souboru.

Řádky a podvodníci

Protože výchozí std. vstup je klávesnice a výchozí std. výstup je obrazovka, rourami většinou „teče“ text, který je více či méně srozumitelný pro lidi. Často v něm každá položka bývá na zvláštním řádku. Právě na taková data je připravena celá řada nástrojů: od filtrů jako sort, který řadí řádky, po Pythoní for radek in soubor: po git diff a jeho ekvivalent na webu GitHub.

Co ale takový ls, který skládá jména souborů za sebe – na jeden řádek jich dá několik?

$ ls -F ..
creatures/  diplomka_archiv/  north-pacific-gyre/  plan.txt   writing/
data/       molecules/        pizza.cfg            solar.pdf

Příkaz ls trochu podvádí: zjistí si jestli má na std. výstupu terminál, a pokud ano, přizpůsobí svůj výpis „lidem“.

Diagram příkazu `ls`, který trochu podvádí

Když ale nemá na std. výstupu terminál, dává každé jméno souboru na zvláštní řádek, aby se výsledek dal dobře zpracovat dalšími filtry:

$ ls -F .. | cat
creatures/
data/
diplomka_archiv/
molecules/
north-pacific-gyre/
pizza.cfg
plan.txt
solar.pdf
writing/

Když si to zkusíš, zjistíš že z výstupu zmizely i barvy. I to je vlastnost, která se špatně zpracovává filtrem, a tak ji ls přidává jen pokud vypisuje do terminálu.

Budeš-li někdy psát vlastní program který něco vypisuje barevně, nezapomeň dát uživatelům možnost barvy vypnout (viz --colorman ls). A ideálně nastav výchozí chování podle toho, jestli je std. výstup terminálem.


Toto je stránka lekce z kurzu, který probíhá nebo proběhl naživo s instruktorem.