Cvičení: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14.
Poslední aktuality jsou v issue #112 (z 15. dubna).
Skript je v Linuxovém prostředí libovolný program, který je interpretován (tj. program je distribuován jako zdrojový kód). V tomto směru pak rozlišujeme shellové skripty (shell je jazyk příkazové řádky), skript v Pythonu, Ruby nebo třeba PHP.
Výhodou tzv. skriptovacích jazyků je, že potřebujete pouze textový editor na vývoj a jsou lehce přenositelné. Nevýhodou pak je, že potřebujete nainstalovat i interpretr. Naštěstí, Linux je často dodáván se sadou interpretrů a začít tak s dalším jazykem je vlastně velmi jednoduché.
Toto cvičení bude postaveno na jednom příkladu, který budeme postupně rozvíjet, abyste se naučili základní koncepty na praktickém příkladu (samozřejmě existují konkrétní nástroje, které by se daly použít rovnou, ale doufáme, že je to lepší než zcela umělý příklad).
Předstartovní kontrola
- Vybrali jste si hezký emulátor terminálu. Jak na školním počítači, tak na své instalaci doma.
- Vybrali jste si pěkný textový editor s TUI, který umíte ovládat. Ujistěte se, že je k dispozici v učebně během cvičení i na vašem počítači a že je správně nakonfigurován.
- Na základní operace se soubory můžete používat
mc
neboranger
. - K procházení souborového systému a kontrole souborů můžete použít
cd
,pwd
,ls
,cat
(ahexdump
).
Průběžný příklad
Data pro náš příklad lze stáhnout z tohoto projektu (které se nachází uvnitř adresáře 03
). Neváhejte si stáhnout celý repozitář jako tarball/ZIP (odkazy jsou pod modrým tlačítkem Code) a dekomprimujte je v mc
.
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ů.
Naším úkolem je napsat program, který vypíše stručný přehled těchto dat:
- Vytiskne 3 nejčastěji navštěvované adresy URL.
- Vytiskne celkové množství přenesených dat.
- Vytiskne 3 dny s největším objemem provozu (tj. součtem přenesených bajtů).
Než ale toto řešení vytvoříme, musíme položit určité základy. A protože jich bude hodně, dokončíme třetí podúkol během příštího cvičení.
Skripty příkazové řádky
Abychom vytvořili shellový skript, stačí psát příkazy do souboru (namísto jejich přímého psaní do terminálu).
Takže, jednoduchý skript, který vypíše informace o systému může vypadat takto.
cat /proc/cpuinfo
cat /proc/meminfo
Pokud tohle uložíte do souboru first.sh
, můžete skript spustit
následujícím příkazem.
sh first.sh
Všimněte si, že spouštíme sh
, což je program s interpretrem, a jméno
vstupního souboru (skriptu).
Udělá to, že spustí cat
s těmito soubory (mohli bychom samozřejmě pustit
cat
jen jednou a dát mu dva argumenty).
Vzpomeňte si, že váš skript 01/dayname.py
lze spustit následujícím
příkazem (opět musíme uvést ten správný interpretr).
python3 dayname.py
Shebang a executable (spustitelný) bit
Spouštění skriptů uvedením interpretru (tj. příkazu, který vlastně skript provede) není úplně elegantní. Ale existuje snazší cesta: označíme soubor jako spustitelný a Linux se postará o zbytek.
Ve skutečnosti, kdy spustíme cat
nebo mc
, existuje soubor (typicky v
adresáři /bin
nebo /usr/bin
), který se jmenuje cat
či mc
a který je
označen jako spustitelný. (Prozatím si představte, že spustitelnost je
speciálním atributem souboru). Všimněte si, že soubor nemá žádnou příponu.
Nicméně, označení souboru jako spustitelného je jen první půlka řešení.
Představte si, že vytvoříme následující program a uložíme ho do
spustitelného souboru hello.py
.
print("Hello")
A teď ho chceme spustit.
Ale počkat! Jak systém pozná, který interpretr má použít? Pro binární aplikace (např. přeložené z Céčkových zdrojáků) je to snadné, protože binárka je (v podstatě) rovnou strojový kód. Ale tady potřebujeme ten správný interpretr.
V Linuxu je interpretr specifikován pomocí tzv. shebangu nebo hashbangu.
Už jste se s ním dokonce několikrát potkali: pokud je první řádka skriptu
označená pomocí #!
(odtud také název: hash a vykřičník), Linux očekává, že
tam najde cestu k interpretru, který se má spustit.
Pro shellové skripty budeme používat #!/bin/sh
, pro Python musíme použít
#!/usr/bin/env python3
. Část s env
vysvětlíme později; zatím si jen,
prosím, zapamatujte, že ji máte používat.
Zpátky k původní otázce: jak je skript tedy spuštěn.
Systém vezme příkaz z shebangu, přidá k němu název souboru se skriptem jako
další parametr a to spustí.
Pokud uživatel přidá více argumentů (např. --version
), jsou přidány také
(na konec).
Například, pokud by hexdump
byl shellový skript, začínal by následujícím:
#!/bin/sh
...
kod-co-jde-po-bajtech-a-tiskne-je-bude-tady
...
Spuštění hexdump -C file.gif
by tedy doopravdy spustilo následující
příkaz:
/bin/sh hexdump -C file.gif
Všimněte si, že vlastně jediná magie za shebangem a spustitelnými soubory je, že systém zkonstruuje delší řádek s příkazem ke spuštění.
A uživatel se nemusí starat o to, který jazyk je vlastně používán.
Vyzkoušejme si to prakticky.
Už ale víme o shebangu, takže soubor upravíme a označíme ho jako spustitelný.
Uložme následující do souboru first.sh
.
#!/bin/sh
cat /proc/cpuinfo
cat /proc/meminfo
Abychom jej označili jako spustitelný, spustíme následující příkaz. Prozatím si jej prosím zapamatujte jako kouzlo, které je třeba provést, podrobnější informace o tom, proč to tak vypadá, přijdou později.
chmod +x first.sh
Teď můžeme skript spustit takto:
./first.sh
Nabízí se otázka: proč to přebytečné ./
místo, aby se zavolal first.sh?
./
se přece vztahuje k aktuálnímu adresáři, ne (vzpomeňte si na předchozí
cvičení)? Odkazuje tedy na stejný soubor!
Když použijeme příkaz (např. cat
) bez určení cesty (tj. jen holé jméno
souboru s programem), shell se podívá do tzv. cesty, která je uložená v
proměnné $PATH, aby našel soubor s tímto programem ($PATH
obvykle bude
obsahovat adresář /usr/bin
kde najdeme většinu spustitelných souborů). Na
rozdíl od jiných operačních systémů se shell nedívá do pracovního adresáře,
když program nenajde v $PATH
.
Pro spuštění programu v aktuálním adresáři proto musíme specifikovat i jeho
cestu (když je soubor uveden s cestou, $PATH
se ignoruje a shell se prostě
pokusí soubor najít). Naštěstí nemusí být absolutní, ale stačí
relativní. Proto ten magický zápis ./
.
Přesuneme-li se do jiného adresáře, můžeme program také spustit pomocí
relativní cesty, např. ../first.sh
.
Spusťte teď ls
v aktuálním adresáři. Měli byste vidět soubor first.sh
vypsaný zeleně. Pokud ne, zkuste ls --color
nebo ověřte, že jste správně
spustili chmod
.
Nemáte-li barevný terminál (neobvyklé, ale pořád možné), můžete použít ls -F
na odlišení typů souborů: adresáře pak budou končit lomítkem,
spustitelné soubory hvězdičkou.
Mini příklady
Změna pracovního adresáře
Trochu teď skript upravíme.
cd /proc
cat cpuinfo
cat meminfo
Spusťte skript znova.
Všimněte si, že navzdory tomu, že skript změnil adresář na /proc
, jsme po
jeho ukončení stále v původním adresáři.
Zkuste vložit pwd
na ověření, že skript je opravdu uvnitř /proc
.
Ladění skriptů
Chcete-li vidět, co se děje, spusťte skript pomocí sh -x first.sh
. Zkuste si to. Pro delší skripty je ale lepší vypisovat vlastní
hlášky, protože -x
bývá až příliš podrobné.
Pro vypsání hlášky na terminál lze použít příkaz echo
. Až na pár
výjimek (více o nich později), jsou všechny jeho argumenty jen vypsány.
Vytvořte skript echos.sh
s následujícím obsahem a vysvětlete rozdíly:
#!/bin/sh
echo alpha bravo charlie
echo alpha bravo charlie
echo "alpha bravo" charlie
Odpověď.
Rozšíření průběžného příkladu
Nyní začneme pracovat na našem průběžném příkladu.
Pro začátek vytvořte verzi, která jednoduše echuje seznam souborů, se kterými budeme pracovat.
Předpokládejte, že program bude číst soubory z podadresáře
logs
. Nezapomeňte nastavit svůj skript jako spustitelný a přidat správný
shebang.
Argumenty na příkazové řádce
Argumenty příkazové řádky (jako například -l
pro ls
nebo -C
pro
hexdump
) jsou obvyklým způsobem, jak ovládat chování CLI nástrojů v
Linuxu. Pro nás, jako vývojáře, je důležité naučit se, jak s nimi uvnitř
našich programů pracovat.
O práci s argumenty v shellových skriptech budeme mluvit později, dnes si ukážeme jejich použití v Pythonu.
Přístup k těmto argumentům v Pythonu je jednoduchý. Potřebujeme do programu
přidat import sys
a poté k nim můžeme přistupovat přes seznam sys.argv
.
Připomeňme si, že import
v jazyce Python nám zpřístupňuje nějakou knihovnu
(tj. obvykle sadu funkcí). sys
nabízí funkce pro práci se systémem pod
interpretem.
Proto tedy následující program vypíše své argumenty.
#!/usr/bin/env python3
import sys
def main():
for arg in sys.argv:
print("'{}'".format(arg))
if __name__ == '__main__':
main()
Při jeho spuštění (samozřejmě poté, co na něj zavoláme chmod +x
) uvidíme
následující (řádky prefixované $
značí příkaz, zbytek je jeho výstup).
$ ./args.py
'./args.py'
$ ./args.py one two
'./args.py'
'one'
'two'
$ ./args.py "one two"
'./args.py'
'one two'
Všimněme si, že nultá položka představuje vlastní název příkazu (ten teď nevyužijeme, ale může být užitečný pro některé chytré triky) a také jak se volání druhého a třetího příkazu liší uvnitř Pythonu.
To by nás ale nemělo překvapit, vzpomeňme si na předchozí cvičení a práci se jmény souborů s mezerami.
Standardní vstup a výstup
Následující pojmy již pravděpodobně znáte, ale možná ne přesně pod těmito názvy, proto se pokusíme osvěžit vaše znalosti o nich.
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.
Rychlá kontrola: jak se v shellu tiskne na stdout? Odpověď.
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.
Tento výstup je obvykle připraven runtimem jazyka (ve kterém byl program napsán) ve spolupráci s operačním systémem. 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:
...
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.
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
.
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ě.
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řipisování na konec souboru při přesměrování výstupu
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
Odpověď.
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
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 spustit následující dva příkazy (a všimněte si rozdílu).
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.
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/sh
echo "Podíváme se na následující soubory:" logs/20[0-9][0-9]-[01][0-9]-[0-3][0-9].csv
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.
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.
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. (Data jsou
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ší.
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.
V pythonovském řešení byste pravděpodobně vytvořili slovník, jehož klíčem by byla adresa URL, hodnotou by bylo počítadlo (kolikrát byla adresa URL navštívena). A pak byste vypsali klíče s nejvyšší hodnotou. Ošklivé řešení, které by se dalo hacknout, aby vše fungovalo, by mohlo vypadat takto (a to předpokládá, že všechny soubory jsou již spojeny):
import sys
urls = {}
for line in map(lambda x: x.rstrip().split(',')[4], sys.stdin):
urls[line] = urls.get(line, 0) + 1
how_many = 3
for url, count in sorted(urls.items(), key=lambda item: item[1], reverse=True):
print("{:7} {}".format(count, url))
how_many = how_many - 1
if how_many <= 0:
break
Náš program se tedy v shellu 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 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 počet výskytů (uniq je sám nesetřídi)
cat logs/*.csv | cut -d , -f 5 | sort | uniq -c
# Setřídíme výstup uniq podle čísel (a v opačném pořadí)
cat logs/*.csv | cut -d , -f 5 | sort | uniq -c | sort -n
# Vytiskneme jen první řádky
cat logs/*.csv | cut -d , -f 5 | sort | uniq -c | sort -n -r | head -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í.
Všimněte si, že řešení v shellu je jednodušší na ladění (jakmile znáte jazyk): sestavujete ho postupně, zatímco ve skriptu Pythonu vyžaduje dodatečné výpisy (které pak musíte odstranit) a řešení je mnohem pevněji zauzlované než řešení v shellu.
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ů
Proč jsou roury tak zajímavé?
Již jsme se zmínili, že roury jsou základními stavebními prvky.
Jejich význam spočívá v možnosti rychle kombinovat různé transformace na více souborech, které jsou ve zcela nekompatibilních formátech.
(Srovnejte to s API v jiných programovacích jazycích, kde je třeba nejprve převést prakticky jakákoli data do nějakého tabulkového formátu, aby bylo možné je filtrovat.)
Nejsou však vhodné pro všechny scénáře. Obvykle se používají k předzpracování dat a interaktivita zde pomáhá, protože můžete snadno sledovat, co se děje. A teprve po dokončení předzpracování předáte data specializovanému nástroji pro další (jemnější) analýzu.
Mimochodem, podle některých průzkumů totiž více než 50 % času věnujete přípravě (např. čištění) dat. Shell se na to velmi hodí.
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
). Všimněte si, že není uveden žádný vstupní soubor.
#!/bin/sh
cut -d : -f 1 | sort
Potom může uživatel skript použít takto a standardní vstup cut
u 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
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
.
První várka příkladů obsahuje také naše řešení, abyste je mohli porovnat se svým.
Druhá várka neobsahuje řešení, ale jsou k dispozici automatizované testy.
Příklady s kompletními řešeními
Příklady s automatizovanými testy
Učební výstupy a kontrola po cvičení
Tato část podává zhuštěný souhrn základních konceptů a dovedností, které byste měli umět vysvětlit a/nebo použít po každém cvičení. Také obsahují absolutní minimum, které je potřebné pro pochopení navazujících cvičení (a dalších předmětů).
Znalosti konceptů
Znalost konceptů znamená, že rozumíte významu a kontextu daného tématu a jste schopni témata zasadit do většího rámce. Takže, jste schopni …
-
vysvětlit, co znamená skript (v kontextu Linuxového prostředí)
-
vysvětlit, co je to shebang a jak ovlivní spuštění skriptu
-
chápat rozdíl mezi tím, zda skript má nebo nemá nastavený spustitelný bit
-
vysvětlit, co je pracovní adresář (working directory)
-
vysvětlit, proč je pracovní adresář soukromou “vlastností” běžícího programu
-
vysvětlit, jak jsou argumenty (parametry) předané skriptu s shebangem
-
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, jak se liší
cat foo.txt
acat <foo.txt
-
vysvětlit, jak může být více programů používajících standardní vstup/výstup složeno (propojeno) dohromady
-
volitelné: vysvětlit, proč
cd
nemůže být normální spustitelný soubor jako/usr/bin/ls
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 …
-
vytvořit Linuxový skript se správným shebangem
-
nastavit executable bit skriptu pomocí utility
chmod
-
přistupovat k argumentům příkazové řádky ve skriptu Pythonu
-
přesměrovat standardní vstup a výstup programů v shellu
-
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