Search
Podklady pro výuku předmětu Operační systémy
Bash (Bourne Again SHell) je interpret příkazů (shell) projektu GNU kompatibilní se starším Bourne shell-em (sh), přidávající užitečné funkce převzaté z Korn shellu (ksh) a C shellu (csh). Shell představuje základní rozhraní pro komunikaci uživatele s operačním systémem počítače. Jeho základní funkcí je spouštění zadaných příkazů a programů, navíc poskytuje řadu vlastních příkazů umožňujících efektivní práci se systémem. Bash je tedy možné používat:
* interaktivně (realizuje příkazovou řádku operačního systému), nebo * jako interpret skriptů.
V interaktivním režimu čte bash příkazy ze standardního vstupu (od uživatele) místo ze souboru, jinak je jeho chování v obou režimech stejné. Skript v jazyce bash je textový soubor obsahující příkazy jazyka bash, oddělené novou řádkou, nebo středníkem. Na první řádku skriptu je vhodné napsat hlavičku, která umožní rozpoznat, že se jedná o skript a jakým jazykem má být skript interpretován. Na operačním systému Linux bude mít tato hlavička tvar
#!/bin/bash
/bin/bash ./muj_skript.sh
Základní funkcí shellu je spouštění externích programů a skriptů, přičemž syntaxe je absolutní nebo relativní cesta k souboru obsahujícímu binární kód, nebo jiný skript. Příkazy se oddělují koncem řádky, nebo středníkem. Znakem # je možné do kódu vkládat komentáře - cokoliv za tímto znakem interpret ignoruje.
Pokud budeme chtít od bashe něco víc, budeme potřebovat proměnné.
Bash nemá datové typy proměnných - resp. s obsahem proměnné se vždy pracuje jako s řetězcem. Proměnná je identifikována názvem, který se může skládat z malých a velkých písmen anglické abecedy, podtržítek a číslic, přičemž nesmí číslicí začínat. Názvy proměnných jsou case-sensitive, což znamená, že promenna a PROMENNA jsou dvě různé proměnné.
Přiřazení hodnoty proměnné se provádí operátorem =, např.
A=24
A =24 # pokus o spuštění příkazu "A" s argumentem "=24" A= 24 # pokus o spuštění příkazu "24" s přednastavenou hodnotou proměnné "A" na prázdný řetězec A = 24 # pokus o spuštění příkazu "A" se dvěma parametry "=" a "24"
echo $A # výpis obsahu proměnné na standardní výstup
echo ${A}
echo x$Ax # vytiskne znak "x" + hodnotu proměnné "Ax" echo x${A}x # vytiskne znak "x" + hodnotu proměnné "A" + znak "x"
Kromě obyčejných proměnných umožňuje bash pracovat s poli. Pole vytvoříme buď deklarací příkazem
declare -a POLE
PRAZDNE_POLE=() # vytvoření prázdného pole v proměnné PRAZDNE_POLE BARVY=(cervena zelena modra) # vytvoření pole se třemi prvky "cervena", "zelena" a "modra" v proměnné BARVY
${BARVY[0]} # vrátí první položku pole
${#BARVY[*]} # vrátí počet položek pole BARVY ${BARVY[*]} # vrátí všechny položky pole BARVY jako jeden řetězec, ve kterém jsou položky pole vzájemně odděleny mezerou ${BARVY[@]} # vrátí všechny položky pole BARVY samostatně (pro správnou funkci je potřeba správně použít uvozovky)
BARVY+=(cerna bila)
Viditelnost proměnné v bashi je omezena na instanci interpretu vykonávajícího aktuální skript. Pokud chceme hodnotu proměnné přenést do synovského procesu (pokud spouštíme z jednoho skriptu jiný skript), můžeme použít příkaz
export PROMENNA
Řetězce v bashi je důrazně doporučeno uzavírat do uvozovek, ačkoliv to syntaxe jazyka striktně nevyžaduje. Uvozovky pomohou především v případech, kdy obsah proměnné obsahuje mezery nebo některé znaky se zvláštní interpretací. Podívejme se na příklad:
A="text s mezerami" program $A program "$A"
Pokud budu chtít předat celé pole jako parametry spuštěného programu (každý prvek jako samostatný argument), musím výraz ${POLE[@]} uzavřít do uvozovek:
program "${POLE[@]}"
Bash umožňuje uzavírat řetězce kromě “dvojitých uvozovek” také do 'apostrofů', přičemž základní rozdíl mezi nimi je ten, že uvnitř apostrofů se neprovádí překlad proměnných na jejich hodnoty, tedy např.
echo "Obsah promenne je $A"
echo 'promenna $A'
Kromě uvozovek a apostrofů umožňuje syntaxe jazyka použití `obrácených apostrofů`, které však neslouží k definici řetězce, ale řetězec uvnitř apostrofů se interpretuje jako příkaz který je vykonán a standardní výstup tohoto příkazu je vložen na místo těcho uvozovek, např.
ADRESAR=`pwd` # spustí příkaz "pwd" (print work directory) a výstup (cestu k aktuálnímu pracovnímu adresáři) uloží do proměnné ADRESAR
ADRESAR=$(pwd)
SOUBORY=( `ls` ) # spustí příkaz "ls" a každý řádek jeho výstupu uloží do jednoho prvku pole SOUBORY (pozor, v případě mezer v názvech souborů nemusí příkaz fungovat správně)
Často požadujeme parametrizaci chování skriptu prostřednictvím argumentů zadaných při spuštění. V některých případech jsou obsahem argumentů zpracovávaná data. Bash umožňuje číst jednotlivé argumenty prostřednictvím speciálních proměnných
$1 # hodnota 1. argumentu $2 # hodnota 2. argumentu $3 # hodnota 3. argumentu atd...
Uvedený způsob můžeme použít pouze pro argumenty 1-9, pro přečtení dalších argumentů je nutné použít zápis se složenými závorkami \${11}, \${12}, atd. Velmi často však požadujeme možnost proměnlivého počtu parametrů skriptu, které nemusí mít pevné pořadí (např. skript pracující se skupinou zadaných souborů, kterých může být libovolný počet). V takovém případě s výhodou využijeme příkaz shift, který odstraní 1. argument a všechny následující posune o jedno místo dopředu (obsahem proměnné \$1 pak bude hodnota 2. argumentu). S využitím cyklů a opekovaného volání příkazu shift potom můžeme postupně zpracovat libovolný počet parametrů. Příkaz shift ovlivňuje i hodnoty proměnných \$# a \$*, resp. \$@.
Bash nám nabízí několik speciálních proměnných, které nám umožňují získat užitečné informace, nebo oblivňovat chování interpretu.
Kromě proměnných pro přístup k argumentům je to proměnná \$?, která obsahuje návratovou hodnotu posledního spuštěného příkazu. V případě, že poslední příkaz obsahoval roury a spouštěl tedy více paralelních procesů, obsahuje proměnná \$? návratovou hodnotu posledního příkazu v kaskádě. Návratové hodnoty předchozích příkazů můžeme získat z proměnné PIPESTATUS, která obsahuje pole návratových hodnot jednotlivých procesů v kaskádě.
Další užitečnou proměnnou je \$PWD, která obsahuje cestu k aktuálnímu pracovnímu adresáři.
Velmi užitečnou proměnnou je IFS (internal field separator), která ovlivňuje rozdělování a spojování řeťezců při mnoha různých operacích. Obsahem proměnné IFS je seznam znaků, které jsou považovány za oddělovače. V případě volání příkazu, který rozděluje vstupní řetězec na pole nebo seznam argumentů je tento řetězec rozdělován v místech výskytu těchto oddělovačů. V případě spojování více řetězců do jednoho jsou spojené řetězce odděleny prvním znakem této proměnné (např. při použití výrazu \$* nebo \${POLE[*]} ). Výchozí nastavení proměnné IFS obsahuje 3 znaky: mezeru, tabulátor a konec řádku. Pokud tedy například budeme číst seznam souborů z výstupu programu “ls” a ukládat je do položek pole, je vhodné předem nastavit proměnnou pouze na konec řádku, čímž zabráníme rozdělení souborů s mezerami v názvu na více položek:
OLD_IFS=$IFS # uschování původního nastavení IFS IFS=$'\n' # tento zápis uloží do proměnné IFS znak "new-line" SOUBORY=( `ls` ) # přečtení názvů souborů v aktuálním adresáři do pole "SOUBORY" IFS=$OLD_IFS # je rozumné nastavit proměnnou IFS na původní nastavení s ohledem na chování ostatních příkazů
Při práci se soubory, zejména v interaktivním uživatelském režimu, často oceníme tzv. globy - vzory umožňující jednoduše vytvořit seznamy souborů s podobnými názvy. Tyto vzory mohou obsahovat následující znaky: * (hvězička) - reprezentuje libovolný řetězec ? (otazník) - reprezentuje jeden libovolný znak […] (seznam znaků v hranatých závorkách) - reprezentuje jeden znak ze zadané množiny V případě že interpret bashe narazí na řetězec obsahující tyto znaky, jsou nalezeny všechny soubory, jejichž názvy odpovídají danému výrazu a seznam názvů těchto souborů je vložen na místo původního výrazu. Pozor, toto nahrazení se používá pouze v případě, že uvedený výraz NENÍ uzavřen v žádných uvozovkách - v opačném případě se výraz použije jako obyčejný řetězec tak jak je. Z toho je zřejmé že použití globů uvnitř skriptu nemusí být rozumné s ohledem na možné nežádoucí chování, bude-li skript pracovat se soubory obsahujícími mezery.
Běžící skript má, stejně jako každý jiný proces, k dispozici svůj standardní vstup, výstup a chybový výstup, jejichž prostřednictvím si vyměňuje data s jinými procesy nebo uživatelem. Nejjednodušší způsob výpisu řetězce na standardní výstup umožňuje příkaz echo. Častěji však využíváme výstupu jiných programů spuštěných z našeho skriptu - pokud jejich výstup explicitně nepřesměrujeme, je automaticky přesměrován na výstup procesu běžícího skriptu. Výpisu na chybový výstup docílíme prostým přesměrováním výstupu příkazu echo (nebo libovolného jiného příkazu):
echo "Chybove hlaseni" >&2
read PROMENNA
Bezpodmínečné ukončení běhu skriptu je možné příkazem exit. Nepovinným parametrem je návratová hodnota, která je vrácena rodičovskému procesu, výchozí návratová hodnota je 0.
Podmínky jsou v bashi realizovány příkazem if, jehož syntaxe je
if <příkaz> then <příkaz1> <příkaz2> ... else <příkaz3> ... fi
test "$A" = "abcd"
if [ "$A" = "abcd" ]; then # podmínka splněna fi
if [ "$A"="abcd" ]; then ... ; fi
if [ $A = "abcd" ]; then ... ; fi
-n STRING ... řetězec je neprázdný -z STRING ... řetězec je prázdný STRING1 = STRING2 ... dva řetězce jsou shodné STRING1 != STRING2 ... dva řetězce jsou rozdílné INTEGER1 -eq INTEGER2 ... dvě čísla jsou si rovná INTEGER1 -ne INTEGER2 ... dvě čísla si nejsou rovná INTEGER1 -gt INTEGER2 ... INTEGER1 je větší než INTEGER2 INTEGER1 -ge INTEGER2 ... INTEGER1 je větší nebo rovno INTEGER2 INTEGER1 -lt INTEGER2 ... INTEGER1 je menší než INTEGER2 INTEGER1 -le INTEGER2 ... INTEGER1 je menší nebo rovno INTEGER2 -e FILE ... existuje soubor s názvem FILE -f FILE ... existuje obyčejný soubor s názvem FILE -d FILE ... existuje adresář s názvem FILE -r FILE ... existuje soubor s názvem FILE a mám práva pro čtení -w FILE ... existuje soubor s názvem FILE a mám práva pro zápis -L FILE ... soubor FILE je existující symbolický odkaz
V případě, že potřebujeme hodnotu proměnné porovnávat s více jinými hodnotami, můžeme použít příkaz case, jehož syntaxe je
case $PROMENNA in hodnota1) <příkaz> <příkaz> ... ;; hodnota2) <příkaz> ;; hodnota3|hodnota4) <příkaz> ;; *) <příkaz> ;; esac
Bash nabízí operátory && a ||, umožnující spojování příkazů do seznamů (též řetězení příkazů) s podmíněným vykonáváním následujících příkazů. Interpretace seznamu říkazů
<příkaz1> && <příkaz2>
Bash nabízí tři druhy cyklů: for, while a until.
Cyklus for slouží k procházení seznamu hodnot a jeho syntaxe je
for PROMENNA in seznam_hodnot do <příkaz> ... done
Cykly while a until mají shodnou syntaxi a rozdíl mezi nimi je pouze ve vyhodnocení podmínky:
while <příkaz> # nebo until <příkaz> do <příkaz> ... done
Uvnitř všech cyklů (tedy uvnitř bloku do … done) je možné používat příkazy pro řízení průchodu cyklem break a continue. Příkaz break způsobí okamžité ukončení cyklu. Příkaz continue přeskočí zbytek příkazů uvnitř cyklu a skočí zpět na začátek cyklu.
Proměnné v bashi jsou řetězce. Pokud do proměnné uložíme celé číslo, uloží se jako řetězec jednotlivých dekadických číslic. Příkaz expr umí převést zadané řetězcové argumenty na čísla a provádět s nimi základní aritmeticé operace. Parametry příkazu expr jsou seznamem operandů, operátorů, případně závorek aritmetického výrazu. Výsledek výpočtu vypisuje expr na standardní výstup. Nejužitečnější operátory jsou sčítání (+), odečítání (-), násobení (*), celočíselné dělení (/) a zbytek po celočíselném dělení (%). Kromě těchto operátorů nabízí navíc relační oprátory (ty už ale umíme realizovat příkazem test) a dále několik operací pro práci s řetězci (ty však budeme schopni provést jednodušeji jiným způsobem). Při zadávání parametrů příkazu expr je nutné důsledně oddělovat mezerami operátory a operandy, jinak je příkaz nerozpozná správně. Volání externího příkazu expr není v rámci skriptu příliš pohodlné a přehledné, proto bash nabízí zkrácený zápis \$( ( výraz )). Tento zápis navíc poskytuje lepší parser vstupního výrazu, nevadí mu chybějící mezery mezi operátory a operandy a navíc automaticky nahrazuje jména proměnných jejich hodnotami. Inkrementace celočíselné proměnné tak může vypadat takto:
A=$(( A+1 ))
let A++
Skupinu příkazů je možné uzavřít do složených závorek, což umožňuje např. hromadné přesměrování vstupu/výstupu pro všechny příkazy:
{ <příkaz> <příkaz> ... } > soubor
Bash nabízí některé základní operace nad řetězci uloženými v proměnné. Délku řetězce v proměnné RETEZEC je možné získat zápisem
echo ${#RETEZEC}
echo ${RETEZEC:<index>:<délka>}
echo ${RETEZEC#zacatek} # odřízne na začátku řetězce $RETEZEC řetězec "zacatek" # (pokud $RETEZEC tímto řetězcem začíná) a vrátí zbytek echo ${RETEZEC%konec} # odřízne z konce řetězce $RETEZEC řetězec "konec" # (pokud $RETEZEC tímto řetězcem končí) a vrátí zbytek
A="abcdefghabcdefgh" echo ${A#*cde} # vypíše "fghabcdefgh" echo ${A##*cde} # vypíše "fgh" echo ${A%cde*} # vypíše "abcdefghab" echo ${A%%cde*} # vypíše "ab"
echo ${RETEZEC/vzor/nahrazeni} # vypíše řetězec z proměnné RETEZEC, přičemž řetězec "vzor" bude nahrazen řetězcem "nahrazeni"
Další možnosti práce s řetězci a texty nabízejí externí nástroje. Nástroj cut umožňuje ze vstupní řádky vyříznout znaky, slova, nebo řetězce oddělené specifikovaným oddělovačem na základě jejich pořadí. Pro hromadné nahrazení znaků za jiné znaky se hodí příkaz tr. Příkaz tr dostává jako parametry dvě stejně dlouhé množiny znaků zadané jako řetězce a nahrazuje ve vstupním textu znaky z první množiny odpovídajícími znaky z druhé množiny. Pro spočítání znaků, slov, nebo řádek ve vstupním textu je možné použít příkaz wc.
Definice funkce v jazyce bash je možná dvěma způsoby:
function nazev_funkce { <příkaz> <příkaz> ... }
nazev_funkce() { <příkaz> ... }
Proměnné uvnitř proměnných mají globální viditelnost. Pokud chceme mít ve funkci proměnnou pouze lokální tak, aby nebyly ovlivňovány proměnné stejného jména vně funkce, je možné explicitně deklarovat funkci jako lokální:
function funkce { local A A=1 } A=2 funkce echo $A # vypíše "2"
Syntaxe volání většiny nástrojů v unixových systémech se řídí doporučeními podle standardu POSIX. Při zpracování jednopísmenných přepínačů dle této syntaxe vám pomůže příkaz getopts. Syntaxe příkazu je getopts options proměnná První paramer (options) je řetězec obsahující seznam písmen podporovaných přepínačů. Pokud má daný přepínač argument, následuje za písmenem dvojtečka. Dvojtečka na začátku řetězce nastavuje tichý mód, ve kterém se nevypisují chyby při zadání neplatného přepínače. Druhým parametrem je název proměnné, do které se uloží písmeno rozpoznaného přepínače. Každé volání příkazu getopt zpracuje jediný argument skriptu, proto je nutné jej volat v cyklu. Použití příkazu je zřejmé z následujícího příkladu:
while getopts ":abchn:s:" opt; do case $opt in a|b|c) echo "Byl aktivovan prepinac -${opt}" ;; n) echo "Prepinac -n ma hodnotu ${OPTARG}" ;; s) echo "Prepinac -s ma hodnotu ${OPTARG}" ;; h) echo "Pouziti skriptu:" echo "`basename $0` [-a] [-b] [-c] [-h] [-n <argument>] [-s <argument>]" ;; ?) echo "Byl zadan neplatny prepinac -${OPTARG}" ;; esac done shift $(($OPTIND - 1)) if [ $# -gt 0 ]; then echo "Ostatni parametry skriptu: $*" fi
Varianta příkazu s názvem getopt umí zpracovávat i volby v dlouhém formátu (např. –help).
Příkaz eval interpretuje řetězec zadaný parametrem jako příkaz, který vykoná. Využijete jej tam, kde si potřebujete připravit složitější příkaz (např. podle zadaných parametrů skriptu) a pak jej vykonat.
Nástroj cat slouží ke spojení obsahu více souborů, zadaných jako parametry, do jednoho výstupu. Pokud není zadán žádný vstupní soubor, čte cat data ze standardního vstupu. Tohoto chování je možné s výhodou využít, pokud píšeme skript, který čte data buď ze zadaného souboru, nebo ze standardního vstupu, není-li soubor zadán.
Příkaz find slouží k rekurzivnímu procházení stromové struktury zadaného adresáře. Nalezené soubory buď vypisuje na standardní výstup, nebo nad nimi vykoná zadaný příkaz. Parametry příkazu find umožňují filtrovat výsledky podle mnoha kritérií (např. jméno nebo typ souboru). Použitím příkazu find se vyhnete implementaci rekurze při procházení podadresářů.
Příkaz dirname vrátí cestu k nadřazenému adresáři pro zadaný soubor nebo adresář. V případě, že zadaná cesta je relativní, vrací také relativní cestu.
Příkaz basename odstraní z cesty k zadanému souboru či adresáři cestu k nadřazenému adresáři a vrátí pouze jeho jméno.
Příkaz readlink umožňuje zjistit, na jaký soubor odkazuje symbolický odkaz. Užitečný je jeho parametr -f, se kterým příkaz vrací absolutní cestu a to i v případě, že není aplikován na odkaz.
Příkaz stat umožňuje získat dostupné infomace o souboru - velikost, čas změny, oprávnění a další informace uložené v I-node. Jeho parametry umožňují libovolné formátování požadovaných informací.
Příkaz diff slouží k porovnání obsahu dvou textových souborů. Na výstup vypisuje nalezené změny ve formátu závisejícím na použitých parametrech.
Příkaz seq generuje posloupnosti čísel v zadaném rozsahu a zadaným krokem.
Příkazy head a tail vrací n prvních resp. posledních řádek zadaného souboru, nebo textu na standardním vstupu, kde n je číslo zadané jako argument parametru -n.
Příkaz sort slouží k seřazení řádků textu (ze souboru nebo standardního vstupu) podle abecedy. Parametry příkazu umožňují změnit způsob řazení.
Příkaz uniq kopíruje řádky standardního vstupu na výstup s vynecháním bezprostředně následujících duplicitních řádků. Parametr -c umožňuje sčítat počty duplicitních řádků.
grep je užitečný nástroj pro filtrování řádek textu podle jejich obsahu. Povinným parametrem příkazu je vzor, se kterým jsou porovnávány vstupní řádky a ty odpovídající jsou vytištěny na výstup. Vzor pro porovnávání je regulární výraz.
awk je nástroj pro zpracování vstupního textu řádku po řádce, přičemž na každou řádku se aplikuje skript napsaný ve vlastním jazyce. Tento skript se skládá z řádek ve tvaru <vzor> { <akce> } Vzor představuje regulární výraz, nebo podmínku, která se vyhodnocuje pro každou řádku vstupu a v případě jejího splnění je na řádek aplikována zadaná akce - příkaz jazyka awk. awk automaticky rozděluje vstupní text na řetězce podle zadaného oddělovače, což dále usnadňuje jeho zpracování. Podporuje proměnné, pole, aritmetické operace a programové konstrukce podobně jako jazyk C (if, while, for …). Dále poskytuje užitečné funkce pro zpracování textu nebo matematické funkce.
sed je nástroj pro zpracování vstupního textu podle programu napsaného ve vlastním jazyce. Jedná se o velmi efektivní nástroj s širokými možnostmi použití. Velmi výhodná je jeho schopnost pracovat s regulárními výrazy pro porovnávání nebo nahrazování textu.