Skriptování v Bournově shellu/Soubory a proudy

Z Wikiknih
◄ Skriptování v Bournově shellu/Řízení běhu Soubory a proudy Skriptování v Bournově shellu/Modularizace ►

Svět Unixu: samé soubory[editovat | editovat zdroj]

Když se zamyslíme, co je to vlastně počítač, začne se nám v mysli skládat seznam o spoustě položek:

  • krabice, v které má počítač vnitřnosti
  • obrazovka
  • klávesnice
  • myš
  • pevný disk, jeho adresáře a soubory
  • síťové připojení
  • tiskárna
  • spousta věcí připojitelný do USB portů
  • a tak dále

To vidíme my. Ale Unix, ten si pohled na svět pořádně zjednodušil. Vidí jen soubory. Prostě je pro něj všechno jen soubor. Nejen data, u kterých jsme na to zvyklí. I adresáře jsou soubory. Celý pevný disk je zase jen soubor. A nejen to, i ta klávesnice, i ta myš, i ta obrazovka a ta tiskárna, jsou jen soubory. Klávesnice je pochopitelně soubor pouze pro čtení a to nekonečný, naopak obrazovka a tiskárna jsou soubory určené jen pro zápis. Zato síťové spojení je pro čtení i pro zápis.

Možná se ptáte: Proč? Kde přišli vývojáři unixových systému k tomu podivínskému nápadu? Proč je vše souborem? Odpověď je jednoduchá: Díky tomu, že je vše souborem, lze také se vším zacházet jako se souborem, a kombinovat různé nástroje pro práci se soubory s libovolnými zařízeními.

A než se přesuneme dál, tak ještě jedna perlička. I procesy, neboli běžící programy, jsou vlastně soubory. Možná si vzpomenete, že jsme si v kapitole o spouštění příkazů říkali, že lze shell ukončit zmáčknutím klávesové kombinace Ctrl+d. A víte, jaký znak tahle kombinace vkládá? Inu ... znak konce souboru!

Co je mezi soubory: proudy[editovat | editovat zdroj]

Ve skutečnosti jsme v předchozí podkapitolce trošku přeháněli. Unix totiž vidí i prostor mezi soubory, tedy mezisouborovou komunikaci. A tomu, jak přetékají data ze souboru do souboru, bit po bitu a bajt po bajtu, se říká proud.

Standardní proudy[editovat | editovat zdroj]

Ve světě Unixu je obvyklé, že se ke každému procesu vážou tři standardní proudy (dokonce ke každému souboru, ale u procesů to dává nejlepší smysl). Procesy mohou proudů samozřejmě používat daleko víc, například různá síťová spojení, spojení na periférie, třeba na tiskárnu atp. Nicméně tři následující standardní jsou k disposici vždy:

Standardní vstup (stdin)
standardní vstupní proud
Standardní výstup (stdout)
standardní výstupní proud
Standardní chybový výstup (stderr)
standardní proud pro chybový výstup.

Podpora pro tyto tři základní proudy je součástí standardních knihoven řady jazyků (například C, C++, Javy nebo Pascalu) a podporuje je samozřejmě i Bournův shell, u kterého je lze dokonce označit za jeden ze základních komunikačních prostředků.

Jak využívat standardní proudy[editovat | editovat zdroj]

Když víme, že Unix nabízí programům standardní proudy, nabízí se otázka, jak se k nim má skript připojit a začít je využívat. Ve skutečnosti tato otázka nemá smysl, protože každý skript už tyto standardní proudy automaticky používá. Když vypisuje na výstup, jedná se právě o standardní výstup, když čte ze vstupu, čte právě ze standardního vstupu. A chybová hlášení shellu jdou automaticky na standardní chybový výstup. Ve všech příkladech, které jsme si ukázali dříve, byly používány právě standardní proudy.

A stejně tak pracoval se standardními proudy i Bournův shell sám, když byl puštěn v interaktivním režimu. Na jeho standardní vstupní proud byl připojen výstup z klávesnice a naopak jeho standardní výstup i standardní chybový výstup byl připojen na konzoli, kterou jste četli.

Jasně! Ale co s tím dál?[editovat | editovat zdroj]

Možná se ptáte, proč se o to zajímat, když to vlastně všechno funguje tak nějak samo. Hned si to řekneme.

Bournův shell totiž nabízí řadu vestavěných nástrojů, pomocí kterých můžeme dělat se vstupy a výstupy pěkné tríčky. Jak vidíme, proudy můžeme propojovat. Například můžeme spojit standardní výstupní proud klávesnice (klávesnicového souboru) se standardním vstupním proudem interaktivního sezení shellu. A standardní výstupní proud i standardní chybový proud připojit na standardní vstup obrazovkového souboru. A nejen můžeme, takové propojení standardního proudu procesu se standardním proudem zařízení je zcela běžnou záležitostí.

Ke všemu můžeme přistupovat jako k souboru se vstupními a výstupními proudy a patřičné ovladače a programy takový přístup obvykle podporují. Můžeme připojit standardní výstup svého interaktivního sezení na standardní vstup tiskárny (místo monitoru, a nebo ještě navíc k monitoru). Můžeme spustit libovolný program a jeho standardní výstup propojit se standardním vstupem tiskárny. Nebo můžeme připojit standardní výstup jednoho programu na standardní vstup jiného programu a to můžeme dále zopakovat a vytvořit tak propojený řetězec programů. A Bournův shell nám nabízí pro vytváření takových propojení podporu.

Pokud máte právě teď pocit, jako kdybyste strčili prsty do elektrické zásuvky, tak to není náhoda – to je surová síla shellu, kterou teď cítíte celým svým tělem …

Přesměrování, aneb používání proudů v shellu[editovat | editovat zdroj]

Jak jsme si všimli v předchozím povídání, Bournův shell už má své standardní vstupy i výstupy automaticky připojené, například na klávesnici a monitor. Podobně je to i u procesů, které sám spouští. Proto se obvykle nemluví o připojování proudů, ale o přesměrování proudů – proudy, které už byly přednastaveným způsobem někam připojeny, chceme připojit jinam. Chceme-li výstup připojit místo na monitor na tiskárnu, mluvíme spíš o přesměrování standardního výstupu na tiskárnu než o jeho připojení na tiskárnu.

Shell nabízí několik operátorů týkajících se přesměrování. Nejjednodušším a zároveň nejuniverzálnějším z nich je rouřítko, kterým můžeme přesměrovávat i mezi procesy a které prozkoumáme později. Ostatní se týkají přesměrování do souboru.

Přesměrování do souboru[editovat | editovat zdroj]

Jak už jsme naznačili, hlavní síla shellu pramení z jeho přirozené schopnosti řetězit jiné programy a používat jejich různé kombinace. Při tom může vyvstat potřeba někam uložit data a tím nejpřirozenějším místem na ukládání dat je souborový systém. Tedy teď myslíme klasické soubory v souborovém systému, nikoliv „všechno“ na základě myšlenky, že „v Unixu je všechno soubor včetně hardware“.

Když mluvíme o řetězení, nabízí se myšlenka, jak ukládat do souboru: Standardní výstup našeho programu připojíme na standardní vstup nějakého programu, který ukládá data ze standardního vstupu do souboru. Máte pravdu, takto by to mohlo bez problémů fungovat. Ale protože ukládání do souboru patří mezi nejběžnější operace v shellu, nabízí pro ně shell zvláštní operátor a tedy není potřeba žádný zvláštní „ukládací“program. Ty operátory pracující se soubory jsou dokonce tři:

proces > soubor
přesměruje výstup procesu do souboru; v případě neexistence soubor vytvoří, v případě existence přepíše jeho starý obsah
proces >> soubor
přesměruje výstup procesu do souboru; v případě neexistence soubor vytvoří, v případě existence připisuje nové na jeho konec
proces < soubor
přesměruje obsah souboru na standardní vstup procesu

Přesměrování výstupu v příkladech[editovat | editovat zdroj]

Podívejme se nyní na několik příkladů přesměrování výstupu. Budeme při nich používat skriptík pozdrav.sh s následujícím obsahem:

#!/bin/sh
echo Ahoj

Jak vidíte, jedná se o skutečně jednoduchý skript, který jen vypíše na svůj standardní výstup Ahoj a pak skončí. Může být spouštěn kterýmkoliv ze způsobů, jež jsme se naučili v kapitole Spouštění příkazů.

$ ./pozdrav.sh > soubor.txt

Když tento příkaz spustíme, zjistíme, že na standardní výstup vůbec nic nenapsal. A tak je to správně! Chtěli jsme, aby byl standardní výstup našeho procesu přesměrován do souboru soubor.txt, tak byl přesměrován tam. Vypsat jeho obsah můžeme příkazem 'cat':

$ cat soubor.txt
Ahoj

Zkusme program pustit znovu, tentokrát s operátorem >>, a pak znovu prozkoumejme obsah výstupního souboru:

$ ./pozdrav.sh >> soubor.txt
$ cat soubor.txt
Ahoj
Ahoj

Jak vidíme, výstup druhého běhu programu byl skutečně připojen na konec již existující souboru, pod výstup prvního běhu. Operátor '>>' pracuje podle očekávání. Zkusme ještě jednou pustit program s přepisovacím operátorem '>'.

$ ./pozdrav.txt > soubor.txt
$ cat soubor.txt
Ahoj

A to je opět podle předpokladu: Předchozí obsah souboru byl přepsán výstupem programu, tedy jediným 'Ahoj'.

Přesměrování vstupu[editovat | editovat zdroj]

Přesměrování výstupu jsme si ukázali, je tedy na čase vyzkoušet přesměrování vstupu, tedy čtení dat ze souboru. Bournův shell nám nabízí operátor '<'.

Přednastavení je přitom takové, že je standardní vstup čten z klávesnice. Zkusme použít příkaz 'cat' bez jakýchkoliv parametrů — bude prostě čekat, až něco napíšete. Vše, co napíšete, pak znovu napíše na standardní výstup. Při tom ovšem může docházet k používání vyrovnávací paměti, takže cat bude vypisovat například vždy až po dokončení řádku. Nebo až poté, co ukončíte zadávání vstupu, k čemuž slouží 'Ctrl+D' (tedy vlastně vložení znaku konce souboru EOF). To ukončí i program 'cat'.

Teď zkusíme použít operátor '<' pro přesměrování vstupu ze souboru:

cat < pozdrav.txt

Podle očekávání vypíše cat obsah vstupního proudu, tedy v tomto případě obsah souboru (opět až po znak konce souboru, který je na jeho konci) a pak se ukončí. Na první pohled to tedy funguje stejně jako volání

cat pozdrav.txt

Ve skutečnosti je tato podobnost dána jenom tím, jak je cat naprogramován. Jiné programy obvykle zacházejí jinak se svým standardním vstupem a jinak se svým prvním parametrem. Věnujme se dále opět přesměrovávání.

Skládání přesměrování souborů[editovat | editovat zdroj]

V rámci jednoho příkazu je možné přesměrovat standardní vstup i standardní výstup, například:

cat < stary_soubor.txt > novy_soubor.txt

což vlastně zkopíruje soubor, tedy provede operaci obvykle prováděnou příkazem

cp stary_soubor.txt novy_soubor.txt

Přesměrování standardního chybového výstupu a dalších proudů[editovat | editovat zdroj]

Doposud jsme zkoumali přesměrování jen těch nejobyčejnějších proudů vztahujících se k souborům, tedy proudy používané, když vše pracuje tak, jak by mělo. Ale co ten další proud určený pro „chyby“? Jak je možné přesměrovat ten? Co kdybychom ho například chtěli zaznamenat do nějakého logovacího souboru?

Pro příklad si vezměme příkaz ls. Pokud spustíte příkaz ls mujsoubor.txt, tak zkrátka zobrazí jméno souboru mujsoubor.txt — ovšem jen pokud takový soubor existuje. Pokud naopak neexistuje, pak ls vypíše chybové hlášení, jež pošle do standardního chybového proudu stderr. Ten ten má stejně jako standardní výstup přednastavené připojení na obrazovku, takže na první pohled ani nepoznáme, že byl právě využit standardní chybový výstup. Ale vyzkoušíme si to.

$ ls mujsoubor.txt
mujsoubor.txt
$ ls neexistujici_soubor.txt
ls: nelze přistoupit k neexistujici_soubor.txt: Adresář nebo soubor neexistuje

Pokud se pokusíme o přesměrování výstupu v případě neexistujícího souboru, tedy pokud napíšeme

$ ls neexistujici_soubor.txt > zaznam.txt

uvidíme, že chybová hláška zřejmě není vypisována na standardní výstup. Soubor zaznam.txt totiž bude prázdný a hláška bude nadále zobrazována na obrazovce, protože jsme přesměrovali pouze standardní výstup a pro hlášku je využíván výstup chybový. A jak tedy přesměrujeme standardní chybový výstup?

Vezměme to poněkud ze široka. Ke každému souboru mohou být připojeny spousty proudů a aby byly jednoznačně rozeznatelné, je ke připojení proudu přiřazeno číslo (file descriptor). Právě zadáním tohoto čísla můžeme vyjádřit, který proud chceme přesměrovat. Na základě tradice je číslo 0 přiřazováno standardnímu vstupnímu proudu, číslo 1 standardnímu výstupnímu proudu a číslo 2 standardnímu chybovému proudu, další čísla jsou volná pro další proudy. Abychom přesměrovali konkrétní soubor, napíšeme před operátor přesměrování číslo jeho připojení. Tedy abychom přesměrovali například standardní chybový výstup, napíšeme před operátor přesměrování číslo 2:

$ ls neexistujici_soubor.txt 2> zaznam.txt

Tentokrát se nedočkáme žádného výstupu na obrazovku, ale v patřičném souboru bude patřičná chybová hláška:

$ cat zaznam.txt
ls: nelze přistoupit k neexistujici_soubor.txt: Adresář nebo soubor neexistuje

Dříve uváděná ukázka

$ cat < stary_soubor.txt > novy_soubor.txt

je ve skutečnosti speciální zkratkou za

$ cat 0< stary_soubor.txt 1> novy_soubor.txt

Číslování proudů nám umožňuje nezávisle přesměrovat standardní a chybový výstup:

$ ls neexistujici_soubor.txt > standardni_vystup.txt 2> chybovy_vystup.txt

Výsledkem bude prázdný soubor standardni_vystup.txt a soubor chybovy_vystup.txt obsahující známou chybovou hlášku.

Je možné také přesměrovat jeden proud do jiného proudu:

$ ls neexistujici_soubor.txt > veskery_vystup.txt 2>&1

Zápis 2>&1 znamená „přesměruj standardní chybový výstup do stejného proudu, kam byl přesměrován standardní výstup“. V tomto případě je pořadí přesměrování velmi důležité! Kdybychom napsali

$ ls neexistujici_soubor.txt 2>&1 > veskery_vystup.txt

tak bychom nejprve přesměrovali standardní chybový výstup tam, kam směřuje standardní výstup (tedy na obrazovku), a pak přesměrovali standardní výstup do souboru. Ve výsledku by tedy nebyly oba výstupy přesměrovány do stejného místa.

Speciální soubory[editovat | editovat zdroj]

Kromě do obvyklých datových souborů můžeme přesměrovávat i do souborů speciálních, které reprezentují například zařízení. Takové soubory jsou v podadresáři /dev. Reprezentují třeba pevný disk nebo USB disk. Také jsou tu speciální soubory jako /dev/null (který všechen vstup nenávratně zahazuje). Do těchto souborů se dá přesměrovávat stejně jako do běžných datových souborů. Je s tím ale být potřeba opatrný: určitě si třeba nechcete přepsat bootovací záznam nějakými experimenty. Ale pokud víte, co děláte, můžete snadno využít speciální soubory pouhým přesměrováním.

Jako příklad si uveďme využití reproduktoru a mikrofonu na Solarisech. Příkaz

cat /tmp/zvuk.au > /dev/audio

přehraje zvuk ze souboru a

cat < /dev/audio > /tmp/zaznam_zvuku.au

Možná se budete muset přihlásit jako root, abyste měli patřičná práva a mohly tyto pokusy provést.

Varování ohledně přesměrování[editovat | editovat zdroj]

Všímavého čtenáře mohly napadnout dosud neprobírané otázky. Například jak je to s přesměrováním dalších proudů kromě těch standardních? Je to možné? Ano, technicky to možné je. Pokud má soubor čtvrtý nebo pátý proud, můžete je přesměrovat. Ale není to dobrý nápad. Pokud bude těch proudů několik, bude těžké odhadnout jejich čísla. A pokud program používá několik proudů, pravděpodobně je pro něj podstatné, aby byly připojeny na patřičná místa.

Další otázkou by mohlo být, jak je to se standardním vstupem programu. Můžeme ho programu „ukrást“, můžeme ho přesměrovat jinam? Můžeme přikázat třeba

cat 0>soubor

Ano, můžeme. Ale těžko pro takovou věc budeme hledat smysluplné využití.

Roury, téčka a pojmenované roury[editovat | editovat zdroj]

Když už umíme přesměrovávat do souborů, můžeme se pustit do něčeho obecnějšího. Budeme přesměrovávat pomocí propojovacích proudů. To je ostatně nejpoužívanější a nejmocnější forma práce s proudy, kterou Bournův shell nabízí. Mluvíme o takzvaných rourách, které se realizují znakem '|', někdy nazývaným v un*xovém světě na základě této funkce „rouřítko“. Roura nám umožňuje vytvořit „potrubí“ procesů, kde je výstup předchozího procesu napojen na vstup následující.

Příklad si ukážeme na příkazu 'grep', který v nejzákladnější podobě volání pro zadané slovo a zadaný text vypíše ty řádky textu, které obsahují dané slovo. A na příkazu 'ps', který vypisuje aktuálně běžící procesy. Pokud zadáte příkaz

ps -eaf

dostanete na výstup obvykle několik obrazovek textu, kde každý řádek popisuje jeden z procesů, které v danou chvíli běží. Prohledávat tak dlouhý seznam očima je samozřejmě náročné, řekněme tedy, že bychom si chtěli usnadnit nalezení informace o procesech, které mají v názvu řetězec 'oracle'. Uděláme to jednoduše – výstup příkazu 'ps' pošleme na vstup programu 'grep', který dostane jako parametr k nalezení řetězec 'oracle', tedy:

ps -eaf | grep oracle

A skutečně, výstupem na obrazovku budou právě ty řádky z výstupu 'ps', které obsahují řetězec 'oracle'. A co když je jich stále ještě moc, než aby se vešly na jednu obrazovku? Můžeme tento výstup poslat do příkazu 'more', který umožní pročítání několikastránkového výstupu:

ps -eaf | grep oracle | more

A teď ještě něco o poznání složitějšího. Řekněme, že bychom chtěli všechny řečené procesy zabít příkazem 'kill'. To můžeme například takto:

ps -ef | grep oracle | awk '{print $2}' | xargs kill -9

Příkaz 'ps' vypíše všechny procesy a výpis je předán 'grep'u, který z něj vybere jen řádky s oraclem. Příkaz awk s danými parametry vybírá z každé řádky jen její druhý sloupec (v tomto příkladě tedy číslo procesu), a příkaz 'xargs' pustí příkaz, který je mu zadán jako parametr, postupně se všemi řádky, které dostane na vstupu, jako s parametrem. A příkaz, který je xargu zadán, zabíjí signálem 9 proces se zadaným číslem.

Rouřítky lze sestavit do potrubí prakticky libovolné množství programů. A i v kombinaci s rouřítku můžeme použít dříve probírané přesměrování:

ps -ef |  grep oracle > /tmp/procesy_oracle.txt

K rourám se váže ještě jeden užitečný příkaz, 'tee'. Jeho název znamená doslova téčko a je poměrně trefný – příkaz svou funkcí připomíná téčko na potrubí. Svůj jediný standardní vstup kopíruje do dvou výstupů: do standardního výstupu a do zadaného souboru. Můžeme ho například vložit na jakékoliv místo potrubí a získáme tím informaci, jak vypadá zpracování v dané fázi.

A ještě jedna důležitá poznámka: Pokud spustíme několik procesů spojených rouřítky, běží souběžně. Samozřejmě, pokud jeden z nich dlouho něco zpracovává a neposílá nic na výstup, tak proces, který na tento výstup čeká na svůj vstup, je v praxi zablokován a technicky vzato neběží, nicméně neformálně řečeno se jedná o paralelní zpracování.

Pojmenované roury[editovat | editovat zdroj]

Kromě bezejmených rour na jedno použití, které vytváříme pouhým napsáním rouřítka a automaticky po použití zanikají, existují také takzvané pojmenované roury. Jedná se vlastně o soubor, který má svůj standardní vstup a výstup, k němuž se připojují procesy. Vytváří se příkazem 'mkfifo', což je možné vyložit jako make fifo, kde fifo znamená first in first out, česky „kdo první přijde, ten první mele“, aneb koncept fronty bez předbíhání.

Ukažme si příklad ve dvou terminálech. V jednom terminálu vytvoříme pojmenovanou rouru a pustíme příkaz cat, který bude mít za úkol vypisovat vše, co do roury přijde:

mkfifo /tmp/roura
cat < /tmp/roura

Příkaz se spustí, ale dál se nebude dít nic: Z roury zatím nepřicházejí žádná data, takže proces čeká.

Pak v druhém terminálu použijeme opět příkaz 'cat', ale tentokrát k tomu, abychom poslali do roury obsah souboru „muj_soubor.txt“:

cat muj_soubor.txt > /tmp/roura

V druhém terminálu se jakoby nic nestane a příkaz bez jakékoliv hlášky skončí, ale když se podíváme na první terminál, zjistíme, že skutečně došlo k vypsání obsahu souboru. A protože byl jeho obsahem i znak konce souboru, příkaz cat se po vypsání souboru ukončil.

Fungovalo by to, i kdybychom k rouře připojili nejprve vstup a až potom výstup.

Virtuální vstupní soubor[editovat | editovat zdroj]

Doposud jsme se zabývali pouze prací s existujícími soubory a proudy. Teď si ukážeme ještě jedno zvláštní přesměrování, které se dělá operátorem <<. Připojuje na vstup data, která se teprve chystáme zadat. Vlevo od operátoru << píšeme cíl, na jehož vstup budou data směřovat, vpravo ukončující řetězec, jehož zadání znamená ukončení vstupu. Vypadá to tedy například takto:

soubor << KONEC
Teď píšeme obsah souboru
a pak si ho v tom souboru můžeme přečíst
KONEC

bude mít výstup

 Teď píšeme obsah souboru
 a pak si ho v tom souboru můžeme přečíst

Součástí zadání virtuálního vstupního souboru mohou být i shellové proměnné, například

COMMAND=cat
PARAM='Ahoj svete!'
$COMMAND <<%
> `echo $PARAM`
> %</source>

Bude mít výstup

Ahoj svete!
◄ Skriptování v Bournově shellu/Řízení běhu Soubory a proudy Skriptování v Bournově shellu/Modularizace ►