BASH
Úvod
Bourne Again SHell je standardní interpret příkazů v Linuxu zaloený na Bourne shell. Funguje jako rozhraní mezi uivatelem a systémem. Jeliko je součástí GNU projektu, nebylo problémem ho portovat na unixové systémy, take se jeho znalost uplatní i mimo Linux. Jeho funkce můeme rozdělit na 3 základní části.
V interaktivním reimu čeká na zadání příkazu od uivatele. Příkazy mohou být buď přímo zabudované v shellu nebo samostatné programy napsané téměř v libovolném programovacím jazyku.
Pomocí systémových proměnných umoňuje přizpůsobení pracovního prostředí. Některé z těchto proměnných jsou přednastaveny systémem, ostatní nastavuje uivatel např. v inicializačních souborech při spuštění shellu.
Je to také velice mocný programovací nástroj. Kdy nám chybí nějaký program nemusíme ho hned psát v kompilovaném jazyku (C, C++, Ada, Java), ale je moné vyřešit náš problém vytvořením skriptu. Můeme si tím ušetřit hodně práce a nebo právě naopak. Nejprve musíme důkladně analyzovat náš problém a zvolit správné řešení.
Zjistěte, jestli máte jako implicitní shell nastaven opravdu BASH. Moností je hned několik. Poslední příkaz zjistí, jaký shell pouívá implicitně váš systém.
$ echo $SHELL |
Jestlie nemáte /bin/bash nastaven jako implicitní shell, napravte to následujícím příkazem a poté spuste BASH, protoe změny se projeví a po přihlášení.
$ usermod -s /bin/bash $USER |
Začínáme
Vypsání hodnot všech proměnných známých aktuálnímu interpretu příkazů (uvedl jsem jen některé z nich, je jich samozřejmě mnohem víc " class="emo">).
$ set |
Proměnná $PS1 definuje tvar primárního promptu (zobrazuje se, kdy shell čeká na zadání příkazu). V definici tvaru proměnných $PSn (n = 1, 2, 3, 4) můeme pouít sekvence se speciálním významem. Ukáeme si pouze některé z nich.
\t - systémový čas (HH:MM:SS)
\u - uivatelské jméno
\W - název pracovního adresáře
\$ - pro roota #, jinak $
$ PS1='[\t] \W\$ ' |
Proměnná $PS2 je tvar sekundárního promptu (zobrazuje se kdy shell čeká na dokončení příkazu). Chcete-li pokračovat v zadávání příkazu na dalším řádku, stačí napsat \ a stisknout ENTER. Středník pouijete při spušení více příkazů najednou.
$ echo "Na dalším řádku je výpis příkazu who"; \ |
Vypsání nastavení různých módů interpretu (uvedl jsem jen dva, je jich opět mnohem víc). Druhý příkaz zapíná mód vi a poslední ho znovu vypíná (takovýmto způsobem lze nastavit všechny módy).
$ set -o |
Běící program můeme ukončit stiskem CTRL+c a standardní vstup (např. v níe uvedeném příkladu) můeme ukončit stiskem CTRL+d, ale nejdříve musíme přejít na nový řádek.
$ wc |
Procesy a signály
Kadý proces má svůj jedinečný identifikátor PID. Spuštěný proces je závislý na svém rodiči (na procesu, ze kterého byl spuštěn). Při ukončení rodiče budou automaticky ukončeni i všichni potomci. Pomocí příkazu nohup zajistíme nezávislost pro nově spouštěný proces a pomocí & ho spustíme na pozadí.
$ nohup ./skript.sh & |
V případě, e nyní ukončíme shell, bude proces s PID 3043 (náš skript) dál pracovat. Proces můeme ukončit zasláním SIGTERM (dovolí procesu uloit data na disk a dobrovolně se ukončit), ale tento signál můe proces ignorovat. Existují dva signály, které ignorovat nemůe, SIGSTOP (pozastaví proces) a SIGKILL (bez milosti proces zabije). Pro zaslání signálu můeme pouít kill nebo killall (POZOR ukončí všechny procesy zadaného názvu!). Pouití ukazují následující příkazy (pouijeme jeden z nich).
$ kill -SIGKILL 3043 |
Stiskem CTRL+z zašleme právě běícímu procesu signál SIGSTOP, zadáním příkazu fg ho opět probudíme a je-li proces na pozadí, umístí ho na popředí. Příkazem bg přesuneme naopak proces na pozadí. Nezadáme-li identifikátor procesu, je pouit poslední pouitý identifikátor v rámci aktivního shellu.
$ mp3blaster |
Speciální soubory
/etc/shells - pouitelné přihlašovací shelly
/etc/adduser.conf - výchozí hodnoty pro adduser
/etc/profile - načítaný při přihlášení
$HOME/.bash_profile - načítaný při přihlášení
$HOME/.bashrc - načítaný při startu interpretu
$HOME/.bash_logout - načítaný při odhlášení
$HOME/.bash_history - evidence naposledy prováděných příkazů
Editace příkazové řádky
Lze jí editovat jako ve dvou nejpouívanějších (dle mého názoru i nejlepších) textových editorech vi, Emacs (není to "pouze" textový editor). Implicitní je mód emacs " class="emo">. Zmíním pouze několik příkazů jako ukázku, zbytek si můete dohledat v dokumentaci.
Ne začnete zkoušet klávesové zkratky, ověřte si, jestli máte zapnutý mód emacs, případně ho zapněte.
$ set -o emacs |
ESC b - posun o jedno slovo zpět
ESC f - posun o jedno slovo vzad
ESC d - smazání následujícího slova
CTRL+Y - vloení naposledy smazané poloky
CTRL+K - smazání textu do konce řádku
CTRL+R - postupné vyhledávání v historii příkazů
ESC < - posun na první řádek historie příkazů
TAB - pokus o obecné dopnění textu
TAB TAB - jestlie existuje více moností doplnění, vypíše je
ESC ~ - pokus o doplnění jména uivatele
CTRL+X ~ - vypíše moné alternativy doplnění jména uivatele
CTRL+X $ - vypíše moné alternativy doplnění jména proměnné
CTRL+X @ - vypíše moné alternativy doplnění jména počítače
ESC TAB - pokusí se doplnit text z předchozích příkazů v histori
Základní příkazy, roury a přesměrování
Popis příkazů nebudu rozebírat do podrobností, od toho máme manuálové stránky. Jen stručně nastíním k čemu jednotlivé příkazy slouí. Abyste věděli, pod kterým příkazem se skrývá vámi poadovaná činnost, a měli jste se na začátku čeho chytit.
Základní příkazy
cp - kopíruje soubory
rm - ruší soubory
mkdir - vytváří adresáře
rmdir - ruší prázdné adresáře
ln - vytvoří odkazy na soubory
chmod - změní přístupová práva k souborům
ls, dir, vdir - vypíše obsah adresářů
find - vyhledávání souborů
which - zobrazí absolutní cestu k programu
df - vypisuje informace o připojených FS
ps - informace o spuštěných procesech
cat, less - výpis souboru na obrazovku
xargs - spustí zadaný příkaz a zbylé argumenty čte ze standardního vstupu
grep - tiskne řádky, které odpovídají zadanému vzoru
wc - vypíše počet písmen, slov a řádků
sort - setřídí řádky
Příklad pouití archivačního programu tar (je to standardní nástroj, take ho naleznete snad v kadé distribuci).
$ tar zcvf archiv.tgz ./adresar |
Mimo archivace tar pouije i kompresi z - gzip, j - bzip2. Volba x - rozbalí archív, c - vytvoří archív, v - vypisuje informace.
Roury
Příkazy dostávájí opravdovou moc teprve díky rourám a přesměrováním. Roura (značí se pomocí operátoru |) připojuje výstup jednoho procesu na vstup 2. procesu.
Přesměrování - operátory přesměrování:
> - přesměrování standardního výstupu do souboru, jestlie soubor existuje bude přepsán
>> - jako předchozí, ale data přídá na konec souboru
< - přesměrování standardního vstupu do souboru
<<text - jako předchozí, ale při výskytu řetězce text zašle znak konce souboru
Chcete-li zabránit přepsání souboru při přesměrování, můete toto implicitní nastavení změnit následujícím příkazem.
$ set -o noclobber |
Před operátorem přesměrování můeme pouít deskriptor souboru.
0 standardní vstup
1 standardní výstup
2 standardní chybový výstup
Dvě ukázky přesměrovaní standardního výstupu a standardního chybového výstupu do stejného souboru. Jako soubor pouijeme /dev/null (o všechno, co do tohoto speciálního souboru přesměrujeme, přijdeme). Zkuste si příklad upravit tak, aby se vám na obrazovku vypisoval jen standardní chybový výstup a pak jen standardní výstup. Před zkoušením si nastavte jako aktuální adresář nějaký, který obsahuje podadresáře a soubory.
$ find | xargs cat &> /dev/null |
První příklad pouití programu tar by šel zapsat i následujícím způsobem za pouití roury a přesměrování do souboru.
$ tar cv ./adresar/ | gzip > archiv.tgz |
Praktický příklad
Potřebujeme vytvořit kontrolní součet všech souborů v aktuálním adresáři a jeho podadresářích za pomocí md5sum a uloit do souboru md5sum.txt (u tohoho souboru nebudeme provádět kontrolní součet).
Ukái vám dvě řešení. To druhé jsem vytvořil, ne jsem se v konferenci dozvěděl o příkazu xargs.
$ find . \! -path './md5sum.txt' -type f | xargs -i md5sum {} > md5sum.txt |
Program find předá programu xargs cestu ke všem souborům (na kadém řádku je cesta k jednomu souboru), ten vezme řádek, dá ho do uvozovek a předá jako argument programu md5sum, načte další řádek... Dokud nezpracuje celý vstup. Standardní výstup programu md5sum se přesměruje do souboru md5sum.txt.
find
\! - neguje následující podmínku
-path './md5sum.txt' - najde soubory, jejich jména odpovídají './md5sum.txt'
-type f - jsou nalezeny běné soubory
xargs
-i - všechny výskyty dvojice znaků {} jsou nahrazeny cestou k souboru ze standardního vstupu, mezery neuzavřené v uvozovkách nejsou povaovány za ukončení argumentu
Je zbytečné psát takhle dlouhý příkaz, kdy ho budeme často pouívat. Proto si do souboru ~/.bashrc přídáme alias.
alias md5sumr='find . \! -path './md5sum.txt' -type f | xargs -i md5sum {} > md5sum.txt' |
Po dalším spustění BASHE stačí, kdy zadáte jen md5sumr.
Druhé řešení je vytvoření skriptu md5sumr.sh. Je to jen pro ukázku, aby bylo vidět, e to jde udělat i mnohem sloitějším způsobem.
#!/bin/bash
koren=$(pwd) vystup="md5sum.txt" cesta="./"
Md5sum() { local tmp
for soubor in *; do if [ "$soubor" == "*" ]; then break fi
if [ -d "$soubor" ]; then cd "./$soubor" tmp="$cesta" cesta="$cesta$soubor/" Md5sum cd "../" cesta="$tmp" else if [ "$soubor" != "$vystup" ] || [ "$cesta" != "./" ]; then pwd=$(pwd) cd "$koren" md5sum "$cesta$soubor" >> "./$vystup" cd "$pwd" fi fi done }
Md5sum |
Proměnné
Jsou pouze jednoho datového typu - řetězec znaků. Některé z nich mohou být určeny jen pro čtení. Proměnné můeme rozdělit do tří částí.
Vnitřní proměnné shellu. O jejich inicializaci se stará shell.
$ echo $USER |
Uivatelské proměnné. Jako výše uvedené proměnné se skládají pouze z alfanumerických znaků.
Proměnné speciálního významu, skládají se ze speciálních znaků. Například:
$$ - PID shellu
$! - PID posledního procesu, který byl spuštěn na pozadí
$? - návratová hodnota posledního dokončeného procesu.
Proměnnou můeme exportovat příkazem export do podřízeného shellu a příkazem readonly zajistíme, e bude určena pouze pro čtení (POZOR, toto omezení se nepřenáší do podřízeného shellu). Kdy chceme získat hodnotu proměnné, napíšeme před ni znak $. Ale kdy jí např. hodnotu přiřazujeme, nepíšeme před ní znak dolaru. Pro odstranění proměnné pouijeme příkaz unset.
$ jedna="Lokální proměnná" |
První skript
Nadešel čas pro napsání a spuštění našeho prvního skriptu. Pak se ještě na chvíli vrátíme k proměnným. Pojmenujeme ho prvni.sh.
#!/bin/bash |
Prvním řádkem zajistíme, e náš skript bude opravdu interpretován BASHEM. To je jediná výjimka při pouití znaku #, řádka začínající tímto znakem je ignorována a slouí k okomentování zdrojového kódu. Kadý správný programátor pouívá ve svých kódech komentáře. Kdy se k němu po čase vrátí, dříve ho pochopí a také zjednoduší pochopení ostatním. Potřeba naučit se správnému pouívání komentářů přijde časem sama. Uvidíte, kde jsou zbytečné a kde naopak velice důleité (POZOR, komentáře se píší ihned se zdrojovým kódem - podle mě není dobrý zvyk je psát a po dokončení programu). A nakonec samozřejmě nezapomeneme vrátit návratový kód exit 0.
Nyní si skript spustíme, ale nejprve musíme přidat právo pro spuštění, protoe textový editor toto právo standardně k nově vytvořeným souborům nepřidává.
$ ls -l |
Proměnné - dokončení
Nyní, kdy umíme spouštět skripty, tak si ukáeme na skriptu promenne.sh ještě několik zajímavých věcí.
#!/bin/bash
prvni="Níe uvedený zá" echo "${prvni}pis umoní oddělit proměnnou od okolního textu"
# Kdyby byla $druha definována, byla by vrácena její hodnota, # jeliko není, bude vrácen "náhradní výraz" echo ${druha-"náhradní výraz"} echo $druha
# To samé jako předchozí, ale $treti nezůstane nedefinovaná echo ${treti="náhradní výraz"} echo $treti
ctvrta="Příšerně luoučký kůň úpěl ďábelské ódy."
# Vrátí "náhradní výraz" je-li proměnná definována, jinak # se nevrací ádná hodnota echo ${ctvrta+"náhradní výraz"} echo $ctvrta
# Vypíše délku $ctvrta echo ${#ctvrta}
# Od konce odstraní nejkraší část $ctvrta, která odpovídá e* echo ${ctvrta%e*}
# Od konce odstraní nejdelší část $ctvrta, která odpovídá e* echo ${ctvrta%%e*}
# Od začátku odstraní nejkraší část $ctvrta, která odpovídá *e echo ${ctvrta#*e}
# Od začátku odstraní nejdelší část $ctvrta, která odpovídá e* echo ${ctvrta##*e}
exit 0 |
Ještě si skript spustíme pro lepší pochopení.
# ./promene.sh |
V shellu si ještě vyzkoušíme několik příkazů, abychom pochopili, jak je to s uvozovkami, apostrofy a expanzí.
$ echo $promenna |
Podmínky
Skript if.sh nám ukáe pouití konstrukce
if výraz; then příkazy elif výraz; then příkazy else příkazy fi
#!/bin/bash
if [ "$USER" == "root" ]; then echo "Ahoj admine"; fi
if [ "$USER" == "root" ]; then echo "Ahoj admine"; else echo "Ahoj uivateli"; fi
if [ "$USER" == "root" ]; then echo "Ahoj admine"; elif [ "$USER" == "fuky" ]; then echo "Ahoj Honzíku"; else echo "Ahoj uivateli"; fi
exit 0 |
POZOR, mezera za [ je důleitá! Znak [ je toti program a to, co následuje za ním, jsou jeho argumenty.
$ which [ |
Jak jsem u jednou říkal, všechny proměnné v shellu jsou jednoho datového typu. To vysvětluje, proč se řetězce a čísla porovnávají níe popsaným způsobem (výraz, výraz1, výraz2 vrací řetězec a teprve kdy ho chceme porovnávat jako číslo, tak ho shell bere jako číslo, jinak to je stále řetězec).
[ výraz ] - délka řetězce je nenulová
[ -z výraz ] - délka řetězce je nulová
[ výraz1 == výraz2 ] - řetězce jsou shodné
[ výraz1 != výraz2 ] - řetězce jsou různé
[ výraz1 -eq výraz2 ] - čísla jsou shodná
[ výraz1 -le výraz2 ] - výraz1 <= výraz2
[ výraz1 -lt výraz2 ] - výraz1 < výraz2
[ výraz1 -ge výraz2 ] - výraz1 >= výraz2
[ výraz1 -gt výraz2 ] - výraz1 > výraz2
[ výraz1 -ne výraz2 ] - čísla jsou různé
Testování souborů
[ výraz1 -ef výraz2 ] - soubory sdílejí stejný i-uzel
[ výraz1 -nt výraz2 ] - první soubor je novější
[ výraz1 -no výraz2 ] - první soubor je starší
[ -e výraz ] - soubor existuje
[ -d výraz ] - soubor je adresář
[ -f výraz ] - soubor je obyčejný soubor
[ -L výraz ] - soubor je symbolický odkaz
[ -w výraz ] - soubor je zapisovatelný
[ -x výraz ] - soubor je spustitelný
Místo [ můete pouívat test. Jsou to stejné programy svázané pevným odkazem.
$ if test /usr/bin/test -ef /usr/bin/\[; then echo "Je to opravdu tak..."; fi |
Podmínky samozřejmě můete spojovat pomocí operátorů && (a zároveň platí) a || (nebo platí).
# if [ $USER == "root" ] && [ $LANG == "cs_CZ" ]; then |
Na skriptu case.sh se podíváme na pouítí konstrukce
case slovo in vzory ) příkazy;; ... esac:
#!/bin/bash
case "$USER" in root ) echo "Ahoj admine" ;; fuky ) echo "Ahoj Honzíku" ;; * ) echo "Ahoj uivateli" ;; esac
case "$USER" in root | fuky ) echo "Ahoj Honzíku" ;; * ) echo "Ahoj uivateli" ;; esac
exit 0 |
Cykly
Pro tento díl poslední skript cykly.sh nás zasvětí do pouívání cyklů for, while a until. Podle mě je dobrým zvykem uzavírat proměnné v podmínkách do uvozovek, protoe kdyby proměnná obsahovala např. mezeru nebo nic, došlo by k chybě.
#!/bin/bash
# Vypíše všechny soubory v adresáři s příponou sh for file in *.sh; do # Soubor je samozřejmě i adresář a co kdy nějaký šílenec # pojmenuje adresář jmeno_adresare.sh if [ -f "$file" ]; then echo $file fi done
# Do $cislo bude postupně dosazovat čísla for cislo in 10 20 30 40 50 60 70 80 90 100; do echo $cislo done
cislo=0 # Podmínka je splněna jestlie $cislo != 100 while [ "$cislo" -ne 100 ]; do # Konstrukci $(()) zavedl shell ksh a je rychlejší a méně # náročná na systémové zdroje ne příkaz expr cislo=$((cislo + 10)) echo $cislo done
cislo=0 # Cyklus pokračuje dokud není splněna podmínka until [ "$cislo" -eq 100 ]; do cislo=$((cislo + 10)) echo $cislo done
exit 0 |
Informace o názvu skriptu, počtu předaných argumentů a argumenty samotné jsou uloeny ve speciálních proměnných.
$0 - název skriptu
$# - počet předaných argumentů
$IFS - seznam znaků, který je pouit k oddělování slov atp., např. kdy shell čte vstup
$1 a $9 - první a devátý argument předaný skriptu
${n} - libovolný n-tý argument předaný skriptu
$* - obsahuje všechny argumenty oddělené prvním znakem z $IFS
$@ - jako předchozí, ale k oddělení se nepouívá první znak z $IFS
Skript argumenty.sh nám poslouí jako ukázka.
#!/bin/bash |
Nyní skript spustíme s 10 argumenty.
$ ./argumenty.sh jedna dva tři čtyři pět šest sedm osm devět deset |
Funkce
Provádění funkcí je mnohem rychlejší ne provádění skriptů, protoe funkce si shell udruje trvale předzpracované v paměti. Funkce musí být definována dříve ne bude pouita. Příkaz export lze pouít i pro funkce, ale musí být zapnutý mód allexport.
$ set -o allexport |
Funkcím můeme předávat argumenty stejně jako skriptům a získáváme je stejným způsobem jako u skriptů. Příkaz return ukončí funkci a vrací její návratovou hodnotu ve formě celočíselného argumentu. Po dokončení funkce jsou poziční argumenty skriptu ($#, $@ ...) obnoveny (u starších shellů to tak být nemusí).
$ funkce_s_argumenty() { |
Budeme-li chtít vrátit řetězcovou hodnotu, můeme to udělat např. níe uvedeným způsobem.
#!/bin/bash
vrat_retezec() { echo "Řetězec" }
promena=$(vrat_retezec) echo $promena
exit 0 |
Pomocí klíčového slova local můeme také vytvořit lokální proměnné funkce. Jestlie bude existovat globální proměnná se stejným názvem, bude ve funkci potlačena.
#!/bin/bash
jedna="První globální proměnná" dva="Druhá globální proměnná"
lokalni_promena() { local jedna="První lokální proměnná"
echo $jedna echo $dva }
lokalni_promena
echo $jedna echo $dva
exit 0 |
Příkazy
Příkazy můeme rozdělit na zabudované a normální. Zabudované příkazy nemůeme spustit jako externí programy, ale většinou mají své ekvivalenty ve formě externích programů. Normální příkazy jsou externí programy a jejich vykonání je pomalejší ne u zabudovaných příkazů.
break - vyskočí z cyklu
: - nulový příkaz
continue - spustí další iteraci cyklu
. - provede příkaz v aktuálním shellu
eval - vyhodnotí zadaný výraz
shift - posune poziční parametry
read - načte uivatelský vstup, jako argument se pouije název proměnné, do které se má uloit
stty - mění a vypisuje charakteristiky terminálové linky
exec - spustí nový shell nebo jiný zadaný program a nebo upraví deskriptor souboru
exit n - ukončení skriptu s návratovým kódem n (n = 0 - úspěšné ukončení, n = 1 a 125 - chyba, ostatní n jsou rezervovány)
printf - není dostupný ve starých shellech a při vytváření formátovaného výstupu byste mu měli dávat přednost před příkazem echo podle specifikace X/Open
Na skriptu prikazy.sh si ukáeme pouití některých výše uvedených příkazů.
#!/bin/bash
for i in 10 20 30 40 50; do if [ $i -eq 40 ]; then break elif [ $i -eq 20 ]; then continue else : fi echo $i done
a="abc" nazev_promene="a"
promena='$'$nazev_promene echo $promena
eval promena='$'$nazev_promene echo $promena
while [ "$1" ]; do echo $1 shift done
exec date
echo "Tato část ji nebude provedena!"
exit 0 |
Nezapomeneme skript spustit s několika argumenty.
$ ./prikazy.sh první druhý třetí |
Nyní si ukáeme interaktivní skript read.sh, který poádá uivatele o zadání přihlašovacího jména a hesla. Heslo se nebude vypisovat na obrazovku.
#!/bin/bash
echo -n "Přihlašovací jméno: " read jmeno
echo -n "Heslo: "
# Vypne výpis vstupních znaků stty -echo
read heslo
# Zapne výpis vstupních stty echo echo
if [ "$jmeno" == "fuky" ] && [ "$heslo" == "heslo" ]; then echo "Kód: Příšerně luoučký kůň úpěl ďábelské ódy" else echo "Nemáte oprávnění k vypsání kódu" fi
exit 0 |
Zadáme-li správné údaje, získáme kód.
$ ./read.sh |
Na závěr tohoto dílu si ukáeme pouití konstrukce
select proměnná in hodnota1 ... hodnotaN; do příkazy; done.
#!/bin/bash
echo "Zadejte vaše pohlaví"
select pohlavi in mu ena; do if [ "$pohlavi" ]; then echo "Jste $pohlavi" break else echo "$REPLY je nedefinovaná odpověď" fi done exit 0 |
Po spuštění příkazu select je uivatel vyzván, aby zadal číslo jedné z hodnot (hodnota1 ... hodnotaN v našem případě mu nebo ena). proměnná $REPLY obsahuje vdy hodnotu uivatelského vstupu. proměnná $pohlavi obsahuje hodnotu pouze v případě, e číslo odpovídá jedné z voleb. Dotaz se opakuje, dokud se neprovede v těle příkaz break.
$ ./select.sh |
Dokumenty here
Umoňují předat vstup příkazu ze samotného skriptu. Ukáeme si to na skriptu here.sh.
#!/bin/bash |
Ještě si skript spustíme.
# ./here.sh $USER=root |
Metaznaky shellu
Lze je pouít k neúplnému zadání jména souboru.
POZOR neztotoňujte metaznaky shellu s regulárními výrazy, jsou to dvě různé věci. Metaznaky expanduje přímo shell. A proto kdy chceme nějakému programu předat regulární výraz, musíme ho uzavřít například do apostrofů.
* - libovolný řetězec (můe být i nulové délky)
? - libovolný jeden znak
~ - domovský adresář ($HOME)
~UJ - domovský adresář uivatele UJ
~+ - aktuální pracovní adresář ($PWD)
~- - předchozí pracovní adresář ($OLDPWD)
[abc...] - jakýkoliv znak uvedený v [], lze pouít - k zápisu intervalu znaků např a-z, 0-9
[!abc...] - opak předchozího (tj. jakýkoliv znak mimo uvedených znaků v [])
První příkaz smae zálohy souborů (soubory končící na ~). Znak ~ nebude v tomto případě expandován.
$ rm *~ |
Regulární výrazy
Jsou (mými slovy, přesná definice je "trochu" sloitější) vzory, s jejich pomocí lze definovat společné rysy několika různých řádků a tím pádem je reprezentovat jako jeden regulární výraz. Níe uvedené speciální znaky jsou pouitelné např. v grep, egrep, sed, ed, ex, awk.
. - jakýkoliv znak (mimo znaku nového řádku)
* - libovolný počet (i nulový) opakování předchozího znaku (lze pouít i regulární výraz)
^ - následující výraz musí odpovídat začátku řádku
$ - předchozí výraz musí odpovídat konci řádku
\ - vypíná speciální význam následujícího znaku
[] - jakýkoliv znak uvedený v hranatých závorkách, speciální znaky zde mají normální význam, mimo - tu lze pouít pro zápis intervalů (a-z atd.) a znak ^ uvedený jako první způsobí negaci (tj. jakýkoliv znak neuvedený v ...)
Pouijeme programy cat, grep a všechno si poctivě vyzkoušíme.
$ cat << END > ./retezce.txt > abclinuxu > alfa > aaa > abcabcabc > znak $ > a1a > aAa > END $ cat ./retezce.txt | grep '.*' abclinuxu alfa aaa abcabcabc znak $ a1a aAa $ cat ./retezce.txt | grep '.* \$' znak $ $ cat ./retezce.txt | grep '^a[a-z]*a$' alfa aaa $ cat ./retezce.txt | grep '^a[a-z0-9]*a$' alfa aaa a1a |
Filtry
Jsou programy, které ze vstupu podle zadaného vzoru odfiltrují jen námi poadovaná data a pošlou je na výstup. Jsou jimi např. grep, egrep (grep -E) a fgrep (grep -F), jsou to vlastně stejné programy. Pro nás je důleité, e grep pouívá pro zápis regulárních výrazů starší notaci a egrep naopak novější notaci. Níe uvedené speciální znaky patří do novější notace a chceme-li je pouít ve filtru grep, musíme před ně zapsat znak \.
+ - jeden a více výskytů předchozího výrazu.
? - jeden nebo ádný výskyt předchozího výrazu.
| - předcházející nebo následující výraz.
() - text odpovídající výrazu mezi závorkami se uloí do paměti a lze ho pouít pomocí \1 a \9, čísluje se od vnějších závorek směrem dovnitř (např. ((abc)linuxu) \1 = "abclinuxu") a \2 = "abc". Nebo lze pouít závorky k definování priority vyhodnocení.
{n,m} - interval opakování předchozího výrazu, {n} - opakuje se n-krát, {n,} n-krát a více, {n,m} n-krát a m-krát
Pro lepší pochopení uvedu opět několik příkladů.
$ cat ./retezce.txt | grep '^a\+$' aaa $ cat ./retezce.txt | egrep '^a+$' aaa $ cat ./retezce.txt | egrep '^abcl?' abclinuxu abcabcabc $ cat ./retezce.txt | egrep '^c|z' znak $ $ cat ./retezce.txt | egrep '(abc)+' abclinuxu abcabcabc $ cat ./retezce.txt | egrep '^(.*)\1\1$' aaa abcabcabc $ cat ./retezce.txt | egrep '^a{3}$' aaa $ cat ./retezce.txt | egrep '^a{2,}$' aaa $ cat ./retezce.txt | egrep '^a{1,3}$' aaa |
Proudové editory
Z názvu je zřejmé, e slouí k proudové editaci dat. O načítání vstupu se starají sami. Mají k dispozici sadu příkazů, pomocí které data upravují (obvykle pracují s jedním řádkem), např. sed a nebo na sloitější věci awk.
Sed
Syntaxe příkazu:
Začátek,Konec!InstrukceArgumenty
Začátek - číslo řádku ($ značí poslední řádek) nebo /regulární výraz/
Konec - číslo řádku nebo /regulární výraz/
! - neguje předchozí body
Instrukce - mají jedno písmeno
Argumenty - k některým instrukcím
Není-li uveden Začátek a Konec, aplikuje se instrukce na kadý vstupní řádek. Je-li uveden pouze Začátek, aplikuje se instrukce pouze na odpovídající řádek (či řádky) a je-li uvedeno obojí, tak od řádku odpovídajícímu Začátek se budou aplikovat instrukce a od řádku odpovídajícímu Konec se aplikovat přestanou. Níe jsou uvedeny některé Instrukce a jejich Argumenty.
s/vzorek/náhrada/příznaky - nahradí první nalezený vzorek náhradou. Příznaky: n - nahradí n-tý výskyt vzorku (1 a 512), g - nahradí všechny výskyty vzorku.
w soubor - do souboru uloí vstupní řádek (řádky)
r soubor - soubor načte do vstupu
p - vypíše vstupní řádek na výstup
n - přesune se na další vstupní řádek
d - vstupní řádek je smazán
y/původní znaky/nové znaky/ - přeloí znaky (man tr)
: - označí řádek skriptu pro odskok Instrukcí t nebo b
t - byla-li provedena substituce, skočí na následující značku :, není-li uvedena, skočí na konec skriptu
{} - zajistí aplikaci více příkazů na jednu adresu
$ cat ./retezce.txt | sed '2,$s/a/?/g' abclinuxu ?lf? ??? ?bc?bc?bc zn?k $ ?1? ?A? $ cat ./retezce.txt | sed -n '2p' alfa $ cat ./retezce.txt | sed -n '1{ > n > p > }' alfa $ cat ./retezce.txt | sed '2p > d' alfa $ cat ./retezce.txt | sed '4y/a/?/ > 4!d' ?bc?bc?bc |
Na závěr uvedu ještě jeden příklad ve formě skriptu sed.sh.
#!/bin/bash
spojka="je bydliště" cat <<EOF | sed \ "s/^\(.\+j\) \(.\+\)o:\(.\+\)\$/\3 $s \1e \2a/ t s/^\(.\+j\) \(.\+\):\(.\+\)\$/\3 $s \1e \2a/ t s/^\(.\+\) \(.\+\)o:\(.\+\)\$/\3 $s \1a \2a/ t s/^\(.\+\) \(.\+\):\(.\+\)\$/\3 $s \1a \2a/" Petr Novák:Praha Viktor Igo:Brno Blaej Vodník:Plzeň Jan Hugo:Hradec Králové Metoděj Sporák:Ostrava EOF
exit 0 |
Výstup skriptu vypadá následovně.
$ ./sed.sh Praha je bydliště Petra Nováka Brno je bydliště Viktora Iga Plzeň je bydliště Blaeje Vodníka Hradec Králové je bydliště Jana Huga Ostrava je bydliště Metoděje Sporáka |
Odchytávání signálů
Signály zaslané skriptu můeme odchytávat pomocí příkazu trap.
trap příkaz signál - jestlie jako příkaz uvedeme znak "-", nastaví se pro signál původní akce a kdy '', neprovede se nic (trap -l vypíše signály, které lze odchytnout).
Vyzkoušejte skript trap.sh.
#!/bin/bash
konec() { echo -n "Uklízím"
i=0 while [ "$i" -le 10 ]; do i=$((i + 1)) echo -n "." sleep 0,1 done
echo echo "Konec" }
trap '' INT echo "Ctrl+C neudělá nic" sleep 3
trap - INT echo "Ctrl+C ukončí skript" sleep 3
trap 'konec; exit 0' INT echo "Ctrl+C spustí funkci konec a ukončí skript" sleep 3
konec
echo "Skript proběhl a do konce"
exit 0 |
Ladění skriptů
Následující módy shellu nám mohou usnadnit ladění.
verbose - před vykonáním příkaz vypíše
xtrace - jako předchozí, ale napřed provede expanzi; $PS4 na začátku řádku určuje stupeň expanze
nounset - je-li pouita nedefinovaná proměnná, ukončí běh skriptu a vypíše chybovou hlášku
#!/bin/bash
set -o verbose echo $PWD
set -o xtrace
echo $PWD
echo $(pwd)
set +o verbose set +o xtrace
set +o nounset echo $nedefinovana_promena
set -o nounset echo $nedefinovana_promena
echo "Tento řádek se ji nevypíše"
exit 0 |
Nyní si skript ladeni.sh spustíme a podíváme se na jeho výpis.
$ ./ladeni.sh echo $PWD /root/fuky/clanky/bash
set -o xtrace
echo $PWD + echo /root/fuky/clanky/bash /root/fuky/clanky/bash
echo $(pwd) pwd ++ pwd + echo /root/fuky/clanky/bash /root/fuky/clanky/bash
set +o verbose + set +o verbose + set +o xtrace
./ladeni.sh: nedefinovana_promena: unbound variable |
Praktické příklady
Úkol 1
Máme libovolnou adresářovou strukturu a v ní jsou uloené soubory *.wav, *.ogg a *.mp3.
*.wav chceme převést do *.ogg a uloit do podadresáře ogg
*.ogg chceme nahradit *.wav
*.mp3 chceme nahradit *.wav
Vytvoříme si skript oggwavmp3.sh.
#!/bin/bash
case "$1" in *.wav ) #cesta="${1%/*}/" cesta=$(echo $1 | sed 's/^\(.\+\/\)\([^/]\+wav\)$/\1/')
if [ -d "${cesta}ogg" ]; then : else mkdir "${cesta}ogg" fi
#soubor="${1%.*}.ogg" #soubor="${soubor##*/}" soubor=$(echo $1 | sed 's/^\(.\+\/\)\([^/]\+\)\.wav$/\2.ogg/')
oggenc "$1" -Q -b 192 -o "${cesta}ogg/$soubor" ;;
*.ogg ) #soubor="${1%.*}.wav" soubor=$(echo $1 | sed 's/\(.\+\)\.\(ogg\)$/\1.wav/')
if [ "$soubor" ]; then sox "$1" "$soubor" rm "$1" fi
;;
*.mp3 ) #soubor="${1%.*}.wav" soubor=$(echo $1 | sed 's/\(.\+\)\.\(mp3\)$/\1.wav/')
if [ "$soubor" ]; then mpg123 "$1" -q -w "$soubor" rm "$1" fi
;;
* ) ;; esac
exit 0 |
Do souboru ~/.bashrc si přidáme alias a po dalším spuštění shellu můeme začít vyuívat náš nový příkaz.
alias oggwavmp3='find -type f | xargs -i ~/bash/oggwavmp3.sh {}' |