3. BASH — Parametry a I/O

BASH — Parametry a I/O #

Osnova cvičení #

  • procvičení psaní skriptů
  • použití nových příkazů (grep, find)
  • přesměrování vstupu a výstupu do souboru či dalšího příkazu

Soubory a streamy #

Přístup k souboru v rámci procesu je identifikován takzvaným “file descriptorem” (fd). File descriptor je celé číslo a platí, že

  • standartní vstup (stdin) má fd rovno 0,
  • standartní výstup (stdout) má fd rovno 1,
  • standartní chybový výstup (stderr) má fd rovno 2.

Z Unixové filosofie vychází, že všechno je soubor, tedy i například ovladače k perifériím (jako sériová linka) - tyto soubory jsou pod adresářem /dev. Pod /dev můžete najít i např. speciální soubory jako

  • /dev/random: zapisuje pouze náhodná čísla
  • /dev/null: nic nevypisuje a zápis do tohoto souboru nic nedělá: používá se pro zahození výstupu

Přesměrování výstupu a vstupu #

Přesměrování souborů #

Může se vám hodit uložit si výstup příkazu do souboru (pro pozdější zkoumání) pomocí operátoru >. Defaultně je přesměrován pouze standartní výstup. Pamatujte si, že když soubor obsahuje data, tak je > všechny přepíše. Pokud si přejete přidávat nová data (tzv. append), tak místo > použijte >>.

cmd > soubor.txt # Přesměruje standartní výstup do soubor.txt

Spojení stderr s stdout se provede pomocí (povšimněte si 1 a 2) 2>&1.

cmd > soubor.txt 2>&1 # Pozor na pořadí

Pro zahození standartního chybového výstupu použijte:

cmd 2>/dev/null

Pro zahození všeho

cmd > /dev/null 2>&1

Pro přesměrování stdout do stderr

cmd >&2

Pro přesměrování obsahu souboru na standartní vstup příkazu použijte:

cmd < vstup.txt

Přesměrování výstupu příkazu do jiného příkazu #

K tomuto se využívá takzvaná roura, angl. pipe. V shellu se zapíše operátorem |. Pozor: přesměruje se opět pouze standartní výstup příkazu.

Ukážeme na příkladu: příkaz grep "MAGIC" soubor.txt vytiskne všechny řádky, které obsahují MAGIC, stdin. Příkaz wc -l vytiskne počet řádek z stdin. Následující příkaz vytiskne počet řádek obsahující MAGIC:

grep "MAGIC" soubor.txt | wc -l

Operátor roury můžete řetězit dále.

Here document #

Pokud si v shellu přejete přesměrovat více řádků najednou do příkazu, musíte použít konstrukci nazývanou “here document”.

Nápověda: příkaz sort -n vytiskne sestupně seřazené řádky podle čísla na začátku řádky zadané ze standartního vstupu

Ukážeme na příkladu, kdy na vstupu z shellu máme seznam neseřazených čísel, na každé řádce jedno číslo. Chceme seřazený seznam vytisknout.

sort -n << EOF
6
2
100
EOF

Pokud chcete toto přesměrovat do souboru, použijte

sort -n << EOF > serazeno.txt
6
2
100
EOF

Text k přesměrování musí být ohraničen, v předchozích 2 příkladech řetězcem EOF. Následující příklad bude také fungovat:

sort -n << FOOBAR
6
2
10
FOOBAR

Here string #

Další užitečnou konstrukcí sloužící pro přesměrování vstupu je operátor <<<, aneb “here string”. Funguje jako here document, ale nevyžaduje ohraničení a většinou se používá pro stringy s jednou řádkou.

Příklad:

grep "Ahoj" <<< "Ahoj svete a PSY"

Dá se také říci, že here document a here string nahrazují konstrukci

echo "RETEZEC" | cmd

Procvičení nových příkazů #

Referenci všech příkazů najdete zde. Ale probereme do detailu některé užitečné příkazy:

  • grep PATTERN: umí vyhledávat PATTERN v textu předaný ze standartního vstupu či souboru. V případě shody vytiskne příslušné řádky. Zajímavé přepínače:
    • -n: vytiskne i číslo řádky
    • -i: case insensitive
    • -r: rekurzivně prohledává adresář pro všechny soubory obsahující PATTERN (nutno předat adresář)
    • -o: vytiskne pouze místo shody, namísto celé řádky
    • vrací 0 pokud něco našel, jinak něco jiného
  • find path: rekurzivně najde soubory v daném podadresáři. Zajímavé přepínače:
    • -name PATTERN: najde soubory splňující PATTERN (např. soubor.txt či "*.txt"). Pokud není tento přepínač specifikován, jsou prohledány všechny soubory
    • -iname PATTERN: to stejné, akorát case insensitive
    • -size VELIKOST: soubory zadané velikosti např. -size -1M nebo -size +1M
    • -mtime CAS: soubory změněné v daném čase, např -mtime -1 změněné za posledních 24 hodin, nebo -mtime +60s změněné déle než 60 sekund.
    • -exec: jestliže je v podadresáři nalezen soubor splňující PATTERN, je vykonán příkaz za -exec na daném souboru. K referenci souboru je potřeba použít {} v příkazu za -exec.
  • tr: (translate characters) jednoduchá manipulace s textem (více man tr)
    • -d: ze vstupu odstraní string za -d
    • -s: stejné znaky za sebou smrskne do jednoho
  • cut: rozdělí text v závislosti na oddělovači a vytiskne konkrétní sekci
    • -d: specifikace oddělovače
    • -f: specifikace pole, která se po oddělení vytiskno

⚠️ K zamyšlení: proč je argument *.txt za -name v uvozovkách?

Příklady příkazů #

grep -rn "printf" src # Ve všech souborech v adresáři src najde výskyty
                      # slova printf, ty řádky vytiskne spolu s číslem řádky.
# grep vrací 0 v případě match - lze použít
if grep $match "soubor.txt"; then
    echo "String $match byl v soubor.txt nalezen"
else
    echo "String $match nebyl v soubor.txt nalezen
fi
# some_logger je nějaký příkaz, který kontinuálně vypisuje
# na standartní výstup log hlášky, grep pouze filtruje ty, které začínají
# na WARNING: a zapisuje je do souboru
some_logger | grep "WARNING:" > ./warning-logs.txt
# pro každý .txt soubor (v . a všech podadresářích) je spuštěn příkaz wc -l
find . -name "*.txt" -exec wc -l {} \; # reference matched souboru pomocí {},
                                       # je nutno zadat \; pro další příkaz

Více příkazů v find může být složitější, viz. Odkazy a další materiály. Můžete použít např. toto řešení

# bash -c spustí nový shell s příkazem za -c
find . -name "*.txt" -exec bash -c "echo \"Nalezen soubor\"; wc -l {}"
# na vstupu máte .csv soubor (sloupce oddělené čárkou)
# úkolem je vypsat název knihy, který je v druhém sloupci
cut -f 2 -d ',' < autori.csv

# toto vypíše všechny sloupce od 2 až dále
cut -f 2- -d ',' < autori.csv

Vstup si můžete stáhnout zde.

Ze studentské tvořivosti #

Co udělá následující příkaz, když proměnná name='a.b.c.d.txt':

echo $name | tr '.' '\n' | tail -n 1

a co tento příkaz:

echo $name | tr '.' '\n' | head -n -1 | tr '\n' '.'

K procvičení #

  1. Napište všechny varianty toho, aby grep přečetl soubor.txt.
  2. Přepište podmínku s grep výše, aby při kontrole grep nevypisoval na stdout. Též pro stderr.
  3. Napište příkaz, který pro všechny soubory končící na .txt vytiskne jejich velikost (použijte du -b {} | cut -f 1). Zkuste ve find napsat to, aby se mimo velikosti vytiskl i samotný soubor.
  4. Napište skript (či složený příkaz), který z autori.csv vytáhne autory. Následně vypíše jejich příjmení (vždy druhé slovo). Příjmení se opakují, tedy pro finální výpis ještě použijte příkaz sort -u (vytiskne lexikograficky seřazené řádky a pokud se nějaká opakuje, tak je vytisknuta pouze jednou)
  5. Napište skript, který v . spočte všechny soubory končící na .txt obsahující text PSY a toto číslo vytiskne. Jestliže nějaký příkaz selže, skript automaticky vrátí 1 a vypíše ERROR. Jestliže bylo nalezeno 0 souborů, skript vrátí 2 a vypíše NO FILES FOUND. Skript nevypisuje nic jiného!
  6. Představte si, že pracujete na projektu a váš konkurent omylem zveřejnil výpis jejich projektu, včetně uživatelských jmen a emailů. Zjistěte všechny jejich usernames a emaily (pro strategické účely), které jsou ve formě
    Jan Novak <novakjan@seznam.cz>.
    
    Pokud budou identické řádky, opět použijte sort -u. Nápověda: zamyslete se, z jakých řádek můžete tuto informaci extrahovat. Jaký bude oddělovač a jaké fieldy použijete?

Odkazy a další materiály #