Dnes se opět ponoříme do Bashe. Už znáš jeho několik základních příkazů a operací. Nově se podíváme, jak vypadají Bashové proměnné, cykly, a jak se píšou skripty.
V materiálech často narazíš na pojem standardní výstup, (angl. standard output stream, zkráceně stdout). Jedná se o místo, kam program vypisuje své výsledky.
Většina programů vypisuje normálně výsledky svých operací přímo do příkazové řádky. Standardní výstup je teminál.
Můžeš ale výstup přesměrovat, např. pomocí >
do souboru nebo pomocí |
do jiného programu.
Některé příkazy přesměrování detekují a chovají se trochu jinak, když je použiješ samostatně, a jinak, když jejich výstup přesměruješ. Srovnej:
$ ls *.txt
NENE01729A.txt NENE01751B.txt NENE01971Z.txt NENE02040A.txt NENE02043B.txt
NENE01729B.txt NENE01812A.txt NENE01978A.txt NENE02040B.txt
$ ls *.txt | cat
NENE01729A.txt
NENE01729B.txt
NENE01736A.txt
NENE01751A.txt
NENE01751B.txt
NENE01812A.txt
NENE01843A.txt
NENE01843B.txt
NENE01971Z.txt
Výstup do terminálu bývá přehlednější pro lidi (a často je i obarvený). Přesměrovaný výstup bývá jednodušší na strojové zpracování.
Zapni terminál a přepni se do adresáře data-shell/north-pacific-gyre/2012-07-03
.
Pomocí ls
se podívej, co v adresáři je:
$ ls
goodiff NENE01736A.txt NENE01843A.txt NENE01978B.txt NENE02040Z.txt
goostats NENE01751A.txt NENE01843B.txt NENE02018B.txt NENE02043A.txt
NENE01729A.txt NENE01751B.txt NENE01971Z.txt NENE02040A.txt NENE02043B.txt
NENE01729B.txt NENE01812A.txt NENE01978A.txt NENE02040B.txt
Úkol: Kolik mají řádků datové soubory s .txt
na konci?
Všechny by měly mít 300 řádků. Když se ale podíváš na výstup, zjistíš, že tomu tak není. Představ si, že máš těchto souborů k ověření několik tisíc a procházení očima nebude možné. Jak zjistíš, jestli všechny tyto soubory mají opravdu 300 řádků?
Soubory končí většinou na písmenko A
nebo B,
ale je zde jeden končící Z
. Spočítej, kolik je souborů končících Z
.
Odpověď
$ ls *Z.txt | wc -l
2
Už podle názvů tyhle soubory nevypadají na to, že by je někdo vytvořil ručně. Můžeš bezpečně předpokládat, že jsou výstupem nějakého programu na zpracování vzorků.
Podívej se do několika z nich (např. pomocí příkazu less
), abys zjistila jak zhruba vypadají informace v nich obsažené.
Obsahem je relativní výskyt 300 bílkovin v různých hloubkách oceánu.
A
a B
jsou dvě hloubky, na nichž bylo prováděno měření; Z
znamená, že se hloubku z nějakého důvodu nepodařilo zjistit.
Kratší soubor je způsobený restarem počítače během měření.
Nevíme, zda takový soubor bude použitelný pro další zpracování.
Už znáš hvězdičku a otazník, díky nimž můžeš vytvořit masky souborů.
Existují i další „finty“, jak jména souborů filtrovat. Jednu z nich si teď ukážeme.
Když dáš do hranatých závorek výčet znaků, které chceš hledat, např. [AB]
, celý výraz v závorkách se nahradí za jedno písmenko – buďto A
nebo B
.
Můžeš tak odfiltrovat soubory, které končí na Z
:
$ ls *[AB].txt
NENE01729A.txt NENE01751A.txt NENE01843A.txt NENE01978B.txt NENE02040B.txt
NENE01729B.txt NENE01751B.txt NENE01843B.txt NENE02018B.txt NENE02043A.txt
NENE01736A.txt NENE01812A.txt NENE01978A.txt NENE02040A.txt NENE02043B.txt
V tomto případě dostaneš stejný výstup, když napíšeš příkaz:
$ ls *A.txt *B.txt
Pozor ale na to, kdyby neexistoval žádný soubor, kterému taková maska odpovídá.
Představ si, že chceš vypsat všechny soubory, které mají na konci A
, B
nebo C
:
$ ls *[ABC].txt
NENE01729A.txt NENE01751A.txt NENE01843A.txt NENE01978B.txt NENE02040B.txt
NENE01729B.txt NENE01751B.txt NENE01843B.txt NENE02018B.txt NENE02043A.txt
NENE01736A.txt NENE01812A.txt NENE01978A.txt NENE02040A.txt NENE02043B.txt
$ ls *A.txt *B.txt *C.txt
ls: cannot access '*C.txt': No such file or directory
NENE01729A.txt NENE01751B.txt NENE01978A.txt NENE02040B.txt
NENE01729B.txt NENE01812A.txt NENE01978B.txt NENE02043A.txt
NENE01736A.txt NENE01843A.txt NENE02018B.txt NENE02043B.txt
NENE01751A.txt NENE01843B.txt NENE02040A.txt
Program ls
si postěžuje, že takové soubory nezná.
Zkus si, co v těchto případech dostává příkaz ls
od Bashe.
To můžeš zjistit tak, že místo ls
zadáš echo
:
$ echo *[ABC].txt
$ echo *A.txt *B.txt *C.txt
Přesuň se do složky data-shell/creatures
.
Jsou v ní tři soubory:
$ ls
basilisk.dat minotaur.dat unicorn.dat
Podívej se, jaké informace tyto soubory obsahují.
Na začátku každého z nich je nějaká informace o zvířeti, které popisují, a následuje část DNA sekvence.
Tvůj úkol je zjistit, jaké informace se nachází u všech těchto souborech na prvních třech řádcích.
Pro tento úkol se může hodit například příkaz head
.
(Všimni si, co head
dělá když mu dáš ke zpracování více souborů.)
$ head -n3 *.dat
==> basilisk.dat <==
COMMON NAME: basilisk
CLASSIFICATION: basiliscus vulgaris
UPDATED: 1745-05-02
==> minotaur.dat <==
COMMON NAME: minotaur
CLASSIFICATION: bos hominus
UPDATED: 1765-02-17
==> unicorn.dat <==
COMMON NAME: unicorn
CLASSIFICATION: equus monoceros
UPDATED: 1738-11-24
Představ si, že pro další zpracování potřebuješ jen druhý řádek každého z těch souboru. U jednoho souboru to půjde jen s příkazy, které už znáš. Zkus to například s baziliškem.
Teď stačí pokaždé přepsat jméno souboru a máš to! To je ale trochu otravné. Jde to zjednodušit? Samozřejmě.
V Pythonu bychom pro takový kus kódu, který se opakuje, použili proměnnou a napsali cyklus. To se teď naučíme dělat v Bashi.
Přiřaď do proměnné jmeno
slovo minotaur.dat
:
$ jmeno=minotaur.dat
Pozor na to, že kolem rovnítka nesmí být mezery!
Když pak chceš použit obsah této proměnné a napíšeš echo jmeno
, co to vypíše?
$ echo jmeno
jmeno
Jak vidíš, není to správná cesta. Je potřeba něco navíc, abys Bashi dala najevo, že chceš skutečně vypsat obsah proměnné jmeno
.
Tuto službu nám zajistí speciální operátor: dolar před jménem proměnné.
$ echo $jmeno
minotaur.dat
Na rozdíl od Pythonu se v Bashi nepoužívá proměnná automaticky.
Když chceš dosadit hodnotu proměnné, musíš použít $
.
Proměnnou můžeš použít jako název souboru.
Následujícím příkazem získáš druhý řádek ze souboru, který je uložený v proměnné jmeno
:
$ head -n2 $jmeno | tail -n1
CLASSIFICATION: bos hominus
Podobně jako v Pythonu můžeš novým přiřazením přepsat obsah proměnné a zpřístupnit tak pro další příkazy jiná data pod stejným jménem.
$ jmeno=unicorn.dat
$ head -n2 $jmeno | tail -n1
CLASSIFICATION: equus monocer
$ jmeno=basilisk.dat
$ head -n2 $jmeno | tail -n1
CLASSIFICATION: basiliscus vulgaris
Pozor, při přiřazování nepiš $
!
Operátor $
v Bashi dosazuje hodnotu proměnné.
Když s proměnnou pracuješ jinak (třeba do ní přiřazuješ), nemá $
u jména co dělat.
(Dolar je taky poslední znak promptu; to je zcela jiný význam stejného znaku.)
Do proměnných se hodí dávat i příkazy, které pak podle potřeby můžeš přepsat něčím jiným.
Představ si, že máš skript, který používá program less
pro čtení souborů.
Jednoho dne ale budeš muset tento skript spustit na počítači starém 30 let, který žádný less
nemá. Má ale archaický program more
.
Takový skript můžeš dopředu nachystat, aby jméno programu bral z proměnné.
Příklad
$ moje_skrolovatko=less
$ $moje_skrolovatko unicorn.dat
$ moje_skrolovatko=more
$ $moje_skrolovatko unicorn.dat
Všimni si, jak se výstup pokaždé mění.
Program less
se vyvinul ze staršího programu more
.
Umí toho mnohem víc než jeho předchůdce, ale potřebuje „moderní“ terminál.
Starší more
si bez problémů vystačí s tiskárnou místo obrazovky.
(Jméno less
vychází z minimalistického hesla less is more.)
Když napíšeš do příkazové řádky víceřádkový příkaz (zahájíš ho otvírací uvozovkou a ukončíš zadávání zavírací uvozovkou), všimni si změny výzvy.
Bude nejspíš vypadat zcela jinak. Poslední znak se většinou změní z $
na >
; to zohledníme tady v materiálech:
$ echo "jeden řádek
> druhý řádek
> třetí řádek"
jeden řádek
druhý řádek
třetí řádek
Pomocí příkazu env
vypíšeš všechny proměnné prostředí, které existují v Bashi. Je jich tam opravdu hodně a určují, jak se chová nejen Bash, ale i programy které spouští.
Ukážeme si několik z nich.
SHELL
- říká, jak máš zrovna shell. Bude to nejspíš /bin/bash
HOSTNAME
- jméno počítačeLC_*něco*
- nastavení formátování různých řetězcůPWD
- aktuální adresářHOME
- domovský adresářVISUAL
- editor, který preferuješPS1
- do této proměnné se dá zapsat, čím nám bude začínat každý řádek v Bashi. Můžeš přepsat např. jen na PS1='$ '
nebo použít zvláštní sekvence jako \w
, což znamená aktuální adresář.PS2
je „pokračovací“ prompt (pro zadávání víceřádkového příkazu)Obsah každé proměnné můžeš vypísat pomocí příkazu echo $<proměnná>
:
$ echo $PS1
Existují také speciální proměnné, které jsou pojmenované různými znaky. Třeba:
$ echo $?
vypíše návratovou hodnotu posledního příkazu.
Příkazy v Unixu vrací hodnoty, podle nichž většinou poznáš, jestli příkaz skončil v pořádku, nebo chybou.
$0
hodnota 0
, pak proběhl příkaz v pořádku.0
značí, příkaz skončil chybouPříklad:
$ ls unicorn.dat
unicorn.dat
$ echo $?
0
$ ls jednorozec.dat
ls: cannot access 'jednorozec.dat': No such file or directory
$ echo $?
2
Na rozdíl od Pythonu Bash není tak striktní, pokud jde o chyby. Nepovedený příkaz ho nezastaví. Je proto dobré si občas kontrolovat, že příkaz proběhl korektně.
Proměnná $$
obsahuje číslo aktuálního shellu. Když otevřeš nový terminál s novým Bashem, uvidíš jiné číslo.
Podobně jako v Pythonu se jména proměnných v Bashi můžou skládat z písmen, čísel a podtržítek. Bash umí jednoduše skládat slova dohromady a přidat tak řetězec před nebo za obsah proměnné:
$ jmeno=minotaur
$ echo $jmeno.abc
minotaur.abc
$ echo my$jmeno
myminotaur
Kdybys ale chtěla dát proměnnou doprostřed do nějakého většího slova, tímto způsobem to nepůjde. Můžeš ale použít složené závorky:
$ echo my${jmeno}.abc
myminotaur.abc
Obecně je $jmeno
zkratka pro ${jmeno}
; varianta se závorkami bude fungovat ve více případech.
Cyklus for
se v Bashi zapisuje podobně jako v Pythonu.
$ for jmeno in a b c
> do
> echo $jmeno
> done
a
b
c
V prvním řádku nechceš zjistit hodnotu proměnné, ale jen do ní přiřazuješ. Proto zde nepoužiješ znak $
. a b c
jsou slova, která se budou postupně přiřazovat do této proměnné.
Po zmáčknutí Enter příkaz pokračuje (poznáš to podle změny výzvy na >
).
Následuje klíčové slovo do
, které označuje zahájení příkazu pro každý z prvků for
cyklu.
Chceme, aby se pro každý průchod cyklem vypsal obsah proměnné jmeno
. Napiš tedy na dalším řádku echo $jmeno
.
Na dalším řádku následuje klíčové slovo done
, označující konec příkazu a konec for
cyklu.
Na rozdíl od Pythonu není nutné tělo cyklu odsadit: blok kódu se uvozuje pomocí slov do
a done
. Hezké odsazení ale zvětšuje přehlednost kódu. Doporučujeme, aby sis na něj zvykla.
Příkaz spusť; vypíše se postupně a
, b
a c
.
Když pak dáš šipku nahoru, místo víceřádkového příkazu ti ho Bash vypíše v jednořádkové formě. Jednotlivé příkazy budou odděleny středníky. Středník a nový řádek v Bashi většinou znamenají totéž.
$ for jmeno in a b c; do echo $jmeno; done
a
b
c
A teď už víš vše pro to, abys vypsala druhý řádek každého souboru! Zkus to udělat.
Kdybys chtěla udělat něco drastičtějšího než jen vypsat části souborů, doporučujeme přidat ještě prostřední krok: použít echo
pro vypsání samotného příkazu a kontrolu, že by se stalo opravdu to, co po počítači chceš.
Příklad:
$ for jmeno in *.dat
> do
> echo rm $jmeno
> done
rm basilisk.dat
rm minotaur.dat
rm unicorn.dat
Po kontrole můžeš smazat echo
a příkaz se provede.
Častá chyba je konstrukce níže, která sice udělá to co chceš, ale zároveň vypíše chybovou hlášku head: cannot open 'ls' for reading: No such file or directory
.
$ for jmeno in ls *.dat
> do
> head -n 2 $jmeno | tail -n 1
> done
head: cannot open 'ls' for reading: No such file or directory
CLASSIFICATION: basiliscus vulgaris
CLASSIFICATION: bos hominus
CLASSIFICATION: equus monoceros
Proč příkaz head
říká, že neumí otevřít soubor ls
? Co se tady stalo?
Odpověď je jednoduchá: Bash interpretuje část ls *.dat
jako seznam slov, nikoli výstup příkazu ls
.
Do proměnné jmeno
tedy přiřadí postupně slova ls
, pak basilisk.dat
, pak minotaur.dat
atd.
Příkaz head -n 2 $jmeno
si pak u ls
stěžuje, že takový soubor neexistuje.
Můžeš taky narazit na jinou chybu. Už víš, že for
cyklus prochází slova, ne příkazy. V příkladu níže jsme zapomněli v těle cyklu uvést echo
. Co se stane, když ho spustíš?
$ for jmeno in cat dog mouse
> do
> $jmeno
> done
To, co je pro první řádek cyklu slovo, se v těle používá jako Bashový příkaz.
Zavolala jsi tedy příkaz cat
bez argumentů, který čeká na vstup z klávesnice.
Ukončíš-li ho klávesovou zkratkou Ctrl+D,
objeví se následně chybový výpis o tom, že příkazy dog
a mouse
nejsou Bashi známé.
V Bashi existuje taky cyklus while
. Jeho syntaxe je podobná cyklu for
.
$ while true; do head /dev/urandom|sha256sum; done
true
je příkaz, který nedělá nic, a jeho návratová hodnota je 0 (tedy „pravda“, „OK“).
Mimochodem: false
je další příkaz, který nedělá nic. Jeho návratová hodnota je ale 1 (tedy „nepravda“, „chyba“).
Několik pomocných zkratek a kláves, které ti usnadní pohyb po příkazové řádce:
history
zobrazí historii zadaných příkazů.cat
a pak procházíš všemi zadanými příkazy, které začínají tímto začátkem. (Není to ale základní součást Bashe; na jiných systémech to nemusí fungovat.)exit
) nebo ukončí vstup pro program (např. pro cat
).K poslednímu triku, pozastavení příkazu, si povězme něco víc.
Otevři interaktivní režim Pythonu a zmáčkni Ctrl+Z
$ python
Python 3.7.5 (default, Nov 20 2019, 09:21:52)
[GCC 9.2.1 20191008] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
[1]+ Stopped python
$
Po pozastavení uvidíš znovu prompt Bashe.
Příkazem jobs
zobrazíš pozastavené příkazy:
$ jobs
[1]+ Stopped python
Příkaz fg
(foreground) tě vrátí k poslednímu zastavenému příkazu.
$ fg
python3
Python naposledy vypsal >>>
a myslí si, že teď zadáváš příkaz. Zmáčkni Enter a >>>
se objeví znovu.
Pak znovu stiskni Ctrl+Z; Python se znovu pozastaví a můžeš zadávat Bashové příkazy.
Příkaz bg
(background) spustí zastavený program na pozadí.
$ bg
[1]+ python3 &
Pomocí kill %<číslo zastaveného programu>
ukončíš konkrétní proces (v našem případě zastavený příkaz číslo 1).
$ kill %1
[1]+ Terminated python3
Pokud spustíš příkaz s &
na konci, spustí se proces přímo na pozadí a umožní ti pracovat dál ve stejné příkazové řádce.
$ gitk --all &
Podobně jako u Pythonu můzeš Bashové příkazy psát do souboru, a pak Bashi říct ať je provede.
Zkus si to se souborem klasifikace
:
$ nano klasifikace
do souboru zapiš:
for x in *.dat
do
head -n 2 $x | tail -n 1
done
Následně můžeš skript spustit. Musíš být ve složce, která obsahuje tento soubor.
Stejně jako u Pythonu spustíš skript pomocí názvu programu, který ho má přečíst (tedy bash
), a názvu souboru, který se má spustit.
$ bash klasifikace
CLASSIFICATION: basiliscus vulgaris
CLASSIFICATION: bos hominus
CLASSIFICATION: equus monoceros
Bashové skripty se dají nastavit i tak, aby byly spustitelné přímo jako příkazy. Proto soubory s Bashovými skripty často nemají příponu. (Když je přípona vhodná, používá se .sh
.)
Udělat ze skriptu příkaz ale není úplně přímočaré. Zkus použit naši klasifikaci jako příkaz:
$ klasifikace
klasifikace: command not found
So s tím?
Na začátek skriptu patří takzvaný shebang, který říká jakým programem se soubor bude spouštět.
V našem případě chceme spouštět Bash.
Na první řádek souboru klasifikace proto připiš dva speciální znaky #!
a jméno souboru, ve kterém je uložený program Bash:
#! /bin/bash
Shebang je instrukce pro systém, že klasifikace
se bude spouštět Bashem.
Pro Bash samotný je to komentář – začíná #
.
(XXX: sem dát mini povídání k $PATH
a nutnost pouštění s ./
)
Další věc, kterou musíš udělat, je nastavení speciálních práv souboru.
ls -l
zobrazí spoustu informací o souboru klasifikace
, ale nás momentálně zajímají především práva, která jsou zobrazena hned vlevo:
$ ls -l klasifikace
-rw-r--r-- 1 user user 58 Oct 15 19:12 klasifikace
Existují tři základní druhy oprávnění, pro které se zobrazí buď písmenko (pokud je oprávnění povoleno) nebo pomlčka (pokud je odepřeno):
Tyto práva se opakují 3x za sebou, jednou pro vlastníka souboru (zde -rw
, vlastník tedy může číst a psát), podruhé pro skupinu (group, zde -r-
, skupina tedy může jen číst), a potřetí pro všechny ostatní uživatele (zde opět můžou jen číst).
Spustitelnému souboru musíš přidat právo na spuštění, což zajistí příkaz chmod +x
. (Přesné chování chmod
si vysvětlíme později.)
$ chmod +x klasifikace
$ ls -l klasifikace
-rwxr-xr-x 1 user user 58 Oct 15 19:12 klasifikace
Po těchto operacích stačí zadát jméno souboru, a on se spustí v Bashi! Jen je ho potřeba zadat s plnou cestou, tedy s ./
na začátku:
$ ./klasifikace
CLASSIFICATION: basiliscus vulgaris
CLASSIFICATION: bos hominus
CLASSIFICATION: equus monoceros
Náš skript klasifikace
zpracovává vždy všechny soubory .dat
v daném adresáři.
Co kdybys chtěla použít tento skript trochu jinak: aby ukázal data jen z daného souboru?
Vytvořme programu klasifikuj
tak, aby fungovalo třeba:
$ ./klasifikuj minotaur.dat
Vytvoř soubor klasifikuj
, dej do něj tento obsah, a přidej právo na spuštění:
#! /bin/bash
head -n2 "$1" | tail -n1
"$1"
je speciální proměnná označující první argument příkazové řádky, tedy u ./klasifikuj minotaur.dat
ono minotaur.dat
.
Proč je v uvozovkách? Protože kdybys zadala jako první argument text s mezerami, uložily by se jako víc argumentů. Když je sekvence takto v uvozovkách, celý první argument se použije správně. Platí obecné pravidlo: pokud bereš odněkud argument, napiš ho pro jistotu do uvozovek.
Kdybys chtěla použít další argumenty, použij "$2"
, "$3"
atd.
A $0
je nultý argument, tedy jméno programu, který se spouští.
$ ./klasifikuj minotaur.dat
CLASSIFICATION: bos hominus
Existuje ještě $@
, což znamená všechny argumenty.
Uprav původní skript klasifikace
tak, aby necyklil po *.dat
, ale "$@"
. Teď ho můžeš spouštět jak pro jeden soubor, tak pro seznam souborů.
$ cat klasifikace
for jmeno in "$@"
do
head -n2 $jmeno | tail -n1
done
$ ./klasifikace unicorn.dat
CLASSIFICATION: equus monoceros
$ ./klasifikace *.dat
CLASSIFICATION: basiliscus vulgaris
CLASSIFICATION: bos hominus
CLASSIFICATION: equus monoceros
Shebang můžeš nastavovat nejen pro Bashové skripty.
Ukážeme si to na příkladě Pythonu.
Vytvoř soubor nahoda
, který bude obsahovat tento kód:
#! /usr/bin/python3
import random
print(random.randrange(100))
Souboru přidej právo na spuštění a můžeš ho spouštět stejně jako klasfikaci z příkladu výše. Shebang zajistí, že se program spustí Pythonem.
$ chmod +x nahoda
$ ./nahoda
34
$ ./nahoda
73
Ukázka výše funguje pro všechny programy, jen musíš vědět, kde přesně na disku jsou (často v /usr/bin/
; do detailů půjdeme později).
Ukažme si to ještě s cat
.
Do souboru README
napiš:
#! /usr/bin/cat
Tady jsou informace.
Přidej práva na spouštění a spusť ho:
$ chmod +x README
$ ./README
#! /usr/bin/cat
Tady jsou informace.
Je to stejné, jako kdybys spustila cat README
!
Možná si teď kladeš otázku, k čemu je v příkladu ./
, když ostatní příkazy zadáváš prostě jménem.
Když zadáš pouze:
$ klasifikace *.dat
klasifikace: command not found
Bash si postěžuje, že nezná takový příkaz.
Bash se totiž dívá jen do určitých adresářů, pokud mu nebylo dáno přesné umístění souboru (což ./klasifikace
je).
Tyto adresáře, které prohledává Bash, jsou obsažené v proměnné PATH
.
Vypiš si její obsah na příkazovou řádku:
$ echo $PATH
/home/user/.local/bin:/home/user/bin:/usr/share/Modules/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/var/lib/snapd/snap/bin
Je v ní spousta adresářů.
Měla bys v mimo jiné vidět /usr/bin
, kde jsou systémové příkazy, a /home/user/bin
, kde můžeš definovat vlastní příkazy.
Jak to funguje? Zkopíruj soubor klasifikace
do adresáře bin
ve své domovské složce (v materiálech je to /home/user/bin/
, ale u tebe se jmenuje jinak).
Pak můžeš skript spouštět jako příkaz, bez ./
na začátku:
$ klasifikace *.dat
CLASSIFICATION: basiliscus vulgaris
CLASSIFICATION: bos hominus
CLASSIFICATION: equus monoceros
V domovském adresáři je skrytý skript ~/.bashrc
, který se spustí vždy, když pustíš Bash.
Je to soubor, ve kterém je možné upravit si prostředí, např. přidat cesty do PATH
, upravit PS1
, nadefinovat si vlastní funkce a spoustu dalších.
Přesuň se do adresáře data-shell/writing
, kde se nachází soubor haiku.txt
$ cat haiku.txt
The Tao that is seen
Is not the true Tao, until
You bring fresh toner.
With searching comes loss
and the presence of absence:
"My Thesis" not found.
Yesterday it worked
Today it is not working
Software is like that.
Náš úkol je podívat se na řádky, kde se nachází slovo not.
Existuje program grep
, kterému když předáš hledané slovo a soubor(y) k prohledání, řádky s daným slovem vypíše.
Následující příkaz tedy hledá v souboru haiku.txt
řetězec not
a vypíše každý řádek s tímto řetězcem:
$ grep not haiku.txt
Is not the true Tao, until
"My Thesis" not found.
Today it is not working
Příkaz grep
má spoustu zajímavých přepínačů, například:
-i
: Nezáleží na velikosti písmen (case insensitive).-n
: Vypíše čísla řádků.-w
: Hledá jen slova.-F
: Hledá přesně zadaný řetězec (viz níže).Takhle můžeš najít všechny řádky se slovem the nebo The:
$ grep -i the haiku.txt
The Tao that is seen
Is not the true Tao, until
and the presence of absence:
"My Thesis" not found.
Ups, je tam ale něco navíc! Jakým příkazem odstraníš z výpisu řádek se slovem Thesis?
$ grep -iw the haiku.txt
The Tao that is seen
Is not the true Tao, until
and the presence of absence:
grep
je velice užitečný program. Pokud pracuješ s Bashem, brzy se z něj stane tvůj nejlepší kamarád.
Pozor ale na to, co grep
bere jako hledaný řetězec, je to totiž regulární výraz (angl. regular expression, často taky regex). Pokud hledáš jen písmenka, nenarazíš na problém, ale u znaků jako tečky, hvězdičky, otazníky může být výstup jiný, než očekáváš.
Zkus několik příkazů:
$ grep -i the.i haiku.txt
$ grep -i '.*' haiku.txt
$ grep -iw '.....' haiku.txt
Speciální funkce znaků můžeš vypnout pomocí přepínače -F
.
S ním bude grep
hledat přesně zadaný řetězec.
Příkaz find
je další užitečný hledací příkaz.
$ find . -name '*.txt'
./haiku.txt
./data/LittleWomen.txt
./data/two.txt
./data/one.txt
Příklad výše hledá v tomto adresáři (.
) a všech podadresářích soubory, které odpovídají masce *.txt
.
Proč je '*.txt'
v uvozovkách?
Vzpomeň si, že znak *
zpracovává samotný Bash: kdybys *.txt
zadala bez uvozovek, find
by dostal jako argumenty všechny *.txt
soubory z aktuálního adresáře.
Uvozovky říkají Bashi, aby příkazu find
předal opravdu *.txt
, a find
tak může odpovídající soubory hledat sám.
Podobného výsledku dosáhneš (ale bez jména adresáře), když zadáš:
$ ls --recursive | grep txt
haiku.txt
LittleWomen.txt
one.txt
two.txt
I bez jména adresáře se ale tento výstup občas hodí – třeba když chceš spočítat, kolik těch souborů je:
$ ls --recursive | grep txt | wc -l
4
Kdykoli ti někdo řekne nebo napíše o zajímavém programu, který si určitě musíš vyzkoušet, buď opatrná.
I v případě, že si ten program nainstaluješ, použij nápovědu pro zjištění, zda opravdu dělá to, co ti bylo řečeno / napsáno.
A právě zde je rozdíl mezi man <jméno neznámého programu>
a <jméno neznámého programu> --help
.
man <jméno neznámého programu>
spouští program man
a jako argument mu dává neznámý program, aniž by ho spustil.
V případě<jméno neznámého programu> --help
říkáš: Hej, neznámý programe, řekni mi něco o sobě., čili spouštíš ho, aby ti ukázal vestavěnou nápovědu. Pokud nevěříš neznámému programu, nemůžeš věřit ani tomuto příkazu.
Inspirace Bashe: https://github.com/encukou/bin
Naklonuj si tyto repozitáře (přímo v adresáři pro tento kurz; git clone
udělá nový podadresář):
$ git clone https://github.com/pyladiescz/pyladies.cz
$ git clone https://github.com/pyvec/pyvo-data
Data si prohlédni a zjisti, co se v nich skrývá za informace. Zvlášť doporučuju třeba soubor pyvo-data/series/brno-pyvo/events/2018-10-25-casove.yaml
.
Použij základní shellové příkazy (ne Python) na zodpovězení těchto otázek:
Z kolika Pyv jsou videa?
Vypiš všechna místa konání Pyv (stačí identifikátor jako artbar
) a kolikrát tam Pyvo bylo.
YAML soubory by se správně měl číst knihovnou na YAML, aby byla zachována struktura. Ty je ale ber jako "čistý text", kde hledané informace jsou na řádcích ve tvaru klíč: hodnota
(příp. s nějakýma mezerama a/nebo pomlčkama navíc). Odpovědi tak nemusí být 100% přesné.
V Pythonu napiš funkci, která bere řetězec a vrátí "obrácený" řetězec: znaky jsou v něm pozpátku a podle následujícího slovníku. Znaky, které ve slovníku nejsou, program vypíše nezměněné.
(Nápověda k Pythonu je níže.)
{'a': 'ɐ', 'b': 'q', 'c': 'ɔ', 'd': 'p', 'e': 'ǝ', 'f': 'ɟ', 'g': 'ƃ',
'h': 'ɥ', 'i': 'ᴉ', 'j': 'ɾ', 'k': 'ʞ', 'l': 'l', 'm': 'ɯ', 'n': 'u',
'o': 'o', 'p': 'd', 'q': 'b', 'r': 'ɹ', 's': 's', 't': 'ʇ', 'u': 'n',
'v': 'ʌ', 'w': 'ʍ', 'x': 'x', 'y': 'ʎ', 'z': 'z', 'A': '∀', 'B': 'B',
'C': 'Ɔ', 'D': 'D', 'E': 'Ǝ', 'F': 'Ⅎ', 'G': 'פ', 'H': 'H', 'I': 'I',
'J': 'ſ', 'K': 'ʞ', 'L': '˥', 'M': 'W', 'N': 'N', 'O': 'O', 'P': 'Ԁ',
'Q': 'Q', 'R': 'R', 'S': 'S', 'T': '┴', 'U': '∩', 'V': 'Λ', 'W': 'M',
'X': 'X', 'Y': '⅄', 'Z': 'Z', '0': '0', '1': 'Ɩ', '2': 'ᄅ', '3': 'Ɛ',
'4': 'ㄣ', '5': 'ϛ', '6': '9', '7': 'ㄥ', '8': '8', '9': '6', ',': "'",
'.': '˙', '?': '¿', '!': '¡', '"': '„', "'": ',', '`': ',', '(': ')',
')': '(', '[': ']', ']': '[', '{': '}', '}': '{', '<': '>', '>': '<',
'&': '⅋', '_': '‾'}
Např.:
>>> obrat("Ahoj, brněnské PyLadies!")
'¡sǝᴉpɐ˥ʎԀ éʞsuěuɹq 'ɾoɥ∀'
Udělej z toho program pro příkazovou řádku, který bere soubory k obrácení. Když nedostane žádný argument, použije standardní vstup. Argument -
taky znamená standardní vstup.
$ echo Ahoj | obrat
ɾoɥ∀
$ echo Ahoj | obrat -
ɾoɥ∀
$ echo 'Ahoj,
> PyLadies!' > pozdrav.txt
$ obrat pozdrav.txt
'ɾoɥ∀
¡sǝᴉpɐ˥ʎԀ
$ echo haha | echo obrat pozdrav.txt - pozdrav.txt
'ɾoɥ∀
¡sǝᴉpɐ˥ʎԀ
ɐɥɐɥ
'ɾoɥ∀
¡sǝᴉpɐ˥ʎԀ
Zařiď, aby s přepínačem --help
program vypsal krátkou nápovědu (a ignoroval ostatní argumenty).
Je-li použit jiný přepínač (začínající -
), program by měl uživateli vynadat (na chybovém výstupu), vrátit chybovou návratovou hodnotu a ignorovat ostatní argumenty.
Nakonec program změň tak, aby vracel chybovou návratovou hodnotu když některý znak chybí ve slovníku.
$ echo Ahoj | obrat
ɾoɥ∀
$ echo $?
0
$ echo Čau | obrat
nɐČ
$ echo $?
1
Naimportuješ-li sys
a os
, pak:
sys.argv
je seznam argumentů (včetně jména programu)sys.stdin
je už otevřený soubor se std. vstupem (netřeba with
či close
)sys.stdout
je soubor se standardním výstupem (tam píše print
) a sys.stderr
je soubor chybovým výstupem.os.environ
je slovník* s proměnnýma prostředíexit(1)
ukončí program s danou hodnotou(* přesněji řečeno, objekt který se chová jako slovník)