Linux: Kniha kouzel, vanilková příchuť 2.14 (15. července 2022)
Veškerá moc příkazové řádky/příkazového řádku přehledně, pro začátečníky i pokročilé

3.1. Bash / Funkce a skripty

Řada 2.x vanilkové příchuti Linuxu: Knihy kouzel je od 15. července 2022 do 1. března 2025 ve stavu dlouhodobé pasivní údržby; nahlášené chyby budou opravovány, ale aktivní vývoj se již věnuje jiným projektům. Máte-li zájem pokračovat v tvorbě Linuxu: Knihy kouzel pro novější verze linuxových operačních systémů, kontaktujte autora nebo rovnou vytvořte odnož.

1. Úvod

Funkce a skripty jsou základními prostředky pro automatizaci a umožňují opakovaně vykonávat komplikované operace pomocí jednoduchých příkazů. Tato kapitola pokrývá jejich vytváření a používání v Bashi a uvádí také nástroje Bashe používané především ve skriptech a funkcích (ačkoliv většinu z nich lze použít i v interaktivním režimu).

Funkce či skript je vždy tvořen uloženou posloupností příkazů, která může být „zavolána“ a přitom jí mohou být předány textové parametry. Hlavní rozdíl mezi funkcí a skriptem je, že funkce se ukládají do paměti konkrétní instance interpretu (podobně jako proměnné), zatímco skript je uložen v souboru (popř. se načítá z roury).

Interpret Bash je vyvíjen v rámci projektu GNU.

2. Definice

  • Funkce je posloupnost příkazů k vykonání uložená v paměti interpretu (podobně jako proměnná). Spouští se zadáním jejího názvu jako příkazu a vykonává se v tomtéž interpretu (tzn. změny proměnných a nastavení provedené ve funkci zůstanou v platnosti i po návratu z ní).
  • Skript je posloupnost příkazů k vykonání uložená v souboru (popř. načítaná z roury). Skript lze spustit několika způsoby (budou probrány níže). Skript se obvykle spustí v nové instanci interpretu (ve stejné instanci ho lze spustit příkazem „source“).
  • Poziční parametry jsou zvláštní číslované proměnné dostupné jako „$1“, „$2“, ..., „$9“, „${10}“ atd. Při volání funkce či skriptu se tyto proměnné na dobu jejího/jeho běhu „překryjí“ textovými hodnotami parametrů, se kterými byla funkce či skript volán; ručně je lze nastavit příkazem „set“, ale pouze všechny najednou.
  • Překryvná proměnná je dočasná proměnná ve funkci, která existuje jen do ukončení jejího volání. Pokud v momentě vytvoření překryvné proměnné již existovala (globální nebo překryvná) proměnná stejného názvu, nová proměnná tu původní na dobu své existence překryje (a tím pádem dočasně znepřístupní); čtení i přiřazení budou pracovat s překryvnou proměnnou a teprve po jejím zániku bude předchozí proměnná znovu zpřístupněna. Překryvné proměnné nejsou lokální — po dobu své existence jsou bez jakékoliv kvalifikace dostupné z celého skriptu, a dokonce mohou být exportovány (jako proměnné prostředí), aniž by to narušilo jejich dočasnost. Pozor, překryvné proměnné nejsou dovoleny mimo funkce (ani ve skriptu).

3. Zaklínadla: Základní

3/1 Logické řetězení příkazů

Poznámka: „příkazem“ se v těchto zaklínadlech rozumí příkaz včetně všech parametrů, přesměrování, rour apod., dokonce to může být i celý blok příkazů ve složených závorkách.

@vykonat příkaz A a po něm příkaz B#1
příkaz A; příkaz B
@a“: vykonat příkaz A, a pokud skončil úspěšně ($? = 0), vykonat příkaz B#2
příkaz A && příkaz B
@nebo“: vykonat příkaz-A, a pokud skončil neúspěšně ($? ≠ 0), vykonat příkaz B#3
příkaz A || příkaz B

3/2 Řízení běhu

@cyklus „while#1 (1)
while testovací-příkazy
do příkazy; cyklu
done [přesměrování]
@cyklus „foreach#2
for identifikator_promenne in parametry
do příkazy; cyklu
done [přesměrování]
@cyklus „for#3
for identifikator_promenne in {počáteční-hodnota..poslední-hodnota[..skok]}
do příkazy; cyklu
done [přesměrování]
@skok za/před konec cyklu#4
break [o-kolik-úrovní]
continue [o-kolik-úrovní]
@podmínka if-else-if#5
if testovací-příkazy
then příkazy větve
[elif testovací-příkazy
then příkazy větve]
[else příkazy větve else]
fi [přesměrování]
@větvení podle shody se vzorkem#6 (2)
case "text" in
(vzorek[|alternativní-vzorek]) příkazy větve ;;
[(vzorek[|alternativní-vzorek]) příkazy větve ;;]
esac [přesměrování]
@cyklus s menu#7 (3)
select identifikator_promenne in parametry
do příkazy; cyklu
done [přesměrování]
@ukončit interpret#8 (4)
exit [návratový-kód]

3/3 Zvláštní proměnné

@návratová hodnota posledního vykonaného příkazu#1 (5)
$?
@pole návratových hodnot příkazů z poslední vykonané roury#2 (6)
${PIPESTATUS[index]}
@text posledního parametru posledního jednoduchého příkazu vykonaného na popředí#3 (7)
$_
@PID/PPID probíhajícího interpretu#4
$$
$PPID
@náhodné číslo v rozsahu 0 až 255/32767/65535/2147483647/4294967295#5
$((RANDOM & 255))
$RANDOM
$((RANDOM << 1 | RANDOM & 1))
$((RANDOM << 16 | RANDOM << 1 | RANDOM & 1))
$((RANDOM << 17 | RANDOM << 2 | RANDOM & 3))
@aktuální počet sloupců/řádek terminálu#6
$COLUMNS⊨ 80
$LINES⊨ 25
@počet sekund od startu této instance interpretu#7
$SECONDS⊨ 7051
@počet sekund od 1. 1. 1970 00:00:00 UTC (celé číslo/desetinné číslo s přesností na mikrosekundy)#8 (8)
$EPOCHSECONDS⊨ 1620459769
$EPOCHREALTIME⊨ 1620459769,984485
@číslo řádku (jen ve skriptu)#9 (9)
$LINENO⊨ 137
@verze Bashe (hlavní/vedlejší číslo)#10
${BASH_VERSINFO[0]}⊨ 5
${BASH_VERSINFO[1]}⊨ 0

3/4 Poziční parametry (skriptů i funkcí)

@dosadit N-tý poziční parametr (alternativy)#1 (10)
$N
${N}
@dosadit všechny parametry od $1: do samostatných parametrů/jen oddělené mezerou#2 (11)
"$@"
"$*"
@dosadit počet pozičních parametrů#3
$#⊨ 0
@smazat N pozičních parametrů počínaje od $1, zbytek přečíslovat#4 (12)
shift [N]
@nastavit poziční parametry#5 (13)
set -- parametr
@dosadit úsek parametrů (samostatně/oddělené mezerou)#6
"${@: číslo-prvního-parametru [: maximální-počet] }"
"${*: číslo-prvního-parametru [: maximální-počet] }"
@dosadit všechny parametry počínaje $0 (samostatně/oddělené mezerou)#7
"${@:0}"
"${*:0}"

3/5 Funkce „zvenku“

@vytvořit (resp. předefinovat) funkci#1 (14)
[function] nazev_funkce {
příkazy funkce
} [přesměrování pro funkci]
@zavolat funkci#2
nazev_funkce [poziční parametry]
@seznam funkcí (s těly/bez těl)#3
declare -f
compgen -A function
@existuje funkce?#4
declare -f nazev_funkce >/dev/null
@zrušit funkci#5
unset -f nazev_funkce
@vypsat definici funkce#6
declare -f nazev_funkce
@exportovat/neexportovat funkci#7 (15)
export -f nazev_funkce
export -fn nazev_funkce

3/6 Funkce „uvnitř“

@vytvořit překryvnou proměnnou#1
local identifikator[=hodnota] [identifikator[=hodnota]]
@uvnitř funkce ve skriptu vypsat „stack-trace“ (jen volání této funkce/všechny úrovně)#2
caller 1
(i=0; while caller $((i++)); do true; done)
@úroveň zanoření funkce (proměnná)#3
$FUNCNEST⊨ 1

3/7 Skript „zvenku“

@spustit skript v nové instanci interpretu (alternativy)#1 (16)
bash [-volby -Bashe] cesta/ke/skriptu [poziční parametry]
cesta/ke/skriptu [poziční parametry]
./relativní/cesta/ke/skriptu [poziční parametry]
@spustit skript v této instanci interpretu#2 (17)
source cesta/ke/skriptu [poziční parametry]
@spustit v nové/této instanci interpretu skript ze standardního vstupu#3 (18)
bash [-volby -Bashe] /dev/stdin [poziční parametry]
source - [poziční parametry]
@spustit v nové instanci „předskript“ a po něm hlavní skript#4 (19)
BASH_ENV=cesta/předskriptu [bash [-volby -Bashe]] cesta/ke/skriptu [poziční parametry]

3/8 Vykonání kódu

@vykonat řetězec jako příkazy v této instanci interpretu#1
eval "řetězec"
@vyhodnotit celočíselný výraz kvůli vedlejším efektům#2
true $((výraz))

3/9 Obsluha signálů a zvláštních situací

@nastavit obsluhu signálu (obecně/příklad)#1
trap [--] 'příkazy' signál
@nastavit prázdnou/výchozí obsluhu signálu#2
trap "" signál
trap - signál
@vypsat nastavené obsluhy všech signálů (přikazy trap)#3
trap
@vypsat obsluhu určitého signálu (příkaz „trap“/text obsluhy)#4
trap -p signál
(function f { test "$1" '!=' trap || shift; test "$1" '!=' -- || shift; printf %s\\n "$1"; }; eval "$(trap -p signál | sed -E 's/trap/f/')")
@nastavit/zrušit obsluhu ukončení interpretu#5
trap 'příkazy' EXIT
trap - EXIT

3/10 Skript „uvnitř“

@číslo řádky skriptu#1 (20)
$LINENO⊨ 137
@verze Bashe (hlavní/vedlejší číslo)#2
${BASH_VERSINFO[0]}⊨ 5
${BASH_VERSINFO[1]}⊨ 0
@běží tato instance interpretu v interaktivním režimu?#3
[[ $- == *i* ]]

4. Zaklínadla: Testy pro podmínky

4/1 Typ a existence adresářových položek

Poznámka: V Bashi můžete všechny uvedené varianty příkazu „test“ nahradit konstrukcí [[ parametry ]], opačně to však neplatí. V interpretu „sh“ je můžete nahradit konstrukcí „[ parametry ]“.

Poznámka 2: Příkaz „test“ s výjimkou volby „-L“ následuje symbolické odkazy; pro vyloučení symbolických odkazů přidejte negovanou volbu „-L“.

@příklad: vytvořit adresář „x“, ledaže taková adresářová položka již existuje#1
test -e x || mkdir x
@existuje položka adresáře?#2
test -e cesta
@je to soubor/neprázdný soubor?#3
test -f "cesta"
test -f "cesta" -a -s "cesta"
@je to adresář?#4
test -d "cesta"
@je to symbolický odkaz?#5
test -L "cesta"
@je to pojmenovaná roura?#6
test -p "cesta"
@je to blokové zařízení/znakové zařízení/soket?#7
test -b "cesta"
test -c "cesta"
test -S "cesta"

4/2 Textové řetězce (testy)

@je řetězec neprázdný/prázdný?#1
test -n "řetězec"
test -z "řetězec"
@jsou si dva řetězce rovny/liší se?#2
test "řetězec 1" = "řetězec 2"
test "řetězec 1" \!= "řetězec 2"
@shoduje se text proměnné se vzorkem Bashe? (obecně/příklad)#3
[[ $název_proměnné = vzorek ]]
[[ $data = "Ahoj, "*'!' ]]
@je pořadí řetězce v seřazeném seznamu menší/větší/shodné#4 (21)
[[ "řetězec 1" < "řetězec 2" ]]
[[ "řetězec 1" > "řetězec 2" ]]
[[ !("řetězec 1" < "řetězec 2" || "řetězec 1" > "řetězec 2") ]]
@má text proměnné alespoň N znaků?#5
test ${#název_proměnné} -ge N

4/3 Regulární výrazy (testy a extrakce)

@obsahuje text proměnné shodu s regulárním výrazem? (bash/sh)#1 (22)
regvyraz='regulární výraz'
[[ $název_proměnné =~ $regvyraz ]]
název_proměnné=$název_proměnné regvyraz='regulární výraz' bash -c '[[ $název_proměnné =~ $regvyraz ]]'
@text první shody po úspěšném testu „[[ text =~ výraz ]]“#2
${BASH_REMATCH[0]}
@text N-tého záchytu první shody (1, 2, ...) po úspěšném testu „[[ text =~ výraz ]]“#3
${BASH_REMATCH[N]}
@pozice první shody po úspěšném testu#4

4/4 Celá čísla (testy)

@=#1
((číslo1 = číslo2))
@<#2
((číslo1 < číslo2))
@>#3
((číslo1 > číslo2))
@#4
((číslo1 <= číslo2))
@#5
((číslo1 >= číslo2))
@#6
((číslo1 != číslo2))

4/5 Čas „změněno“ u souborů a adresářů

@je soubor1 novější než soubor2? (>)#1
test "soubor1" -nt "soubor2"
@je soubor1 stejně starý nebo novější než soubor2? (≥)#2
test ! "soubor1" -ot "soubor2"
@je soubor1 stejně starý jako soubor2? (=)#3
test ! "soubor1" -ot "soubor2" -a ! "soubor1" -ot "soubor2"
@je soubor1 stejně starý nebo starší než soubor2 (≤)#4
test ! "soubor1" -nt "soubor2"
@je soubor1 starší než soubor2? (<)#5
test "soubor1" -ot "soubor2"

4/6 Práva adresářových položek

Poznámka: testy -r, -w a -x netestují nastavená přístupová práva, ale faktickou proveditelnost dané operace; proto „w“ vrátí „nepravdu“ u souboru s právem „w“, který se nachází na oddílu připojeném jen pro čtení, a test „r“ provedený superuživatelem vrátí „pravdu“ u souboru, který nesmí číst nikdo „chmod a-r“.

@můžeme ji/ho číst?#1
test -r "cesta"
@můžeme do ní/něj zapisovat?#2
test -w "cesta"
@můžeme ji/ho spouštět, resp. vstoupit do adresáře?#3
test -x "cesta"
@má nastavený „sticky“/„set-user-id“ bit?#4
test -k "cesta"
test -u "cesta"

4/7 Ostatní testy

@znamenají dvě adresářové položky tutéž entitu? (následuje symbolické odkazy)#1 (23)
test cesta1 -ef cesta2
@je proměnná definovaná? (jen bash)#2
[[ -v název_proměnné ]]

5. Instalace na Ubuntu

Bash a všechny příkazy použité v této kapitole jsou základními součástmi Ubuntu přítomnými i v minimální instalaci.

6. Tipy a zkušenosti

  • Klávesové zkratky Ctrl+C a Ctrl+C se chovají nečekaným způsobem při provádění cyklu nebo volání funkce z interaktivního interpretu. Pokud se tomu chcete vyhnout, uzavřete při volání funkci či cyklus do podprostředí.
  • Funkce na rozdíl od aliasů nemají ochranu proti rekurzi, takže funkce může volat sama sebe. Pokud chcete funkcí nahradit nějaký externí příkaz a pak ho z ní volat, použijte vestavěný příkaz „command“.
  • Bash nabízí několik možných syntaxí k definici funkce. Definice s příkazem „function“ je dle mého názoru nejsnáze čitelná, ale není příliš běžná.

7. Další zdroje informací

8. Zákulisí kapitoly

V této verzi kapitoly chybí:

  • getopt
  • proměnná SHLVL

Tato kapitola záměrně nepokrývá:

  • Práci s proměnnými (viz kapitolu Proměnné příkazy a interpretu)

Další poznámky:

  • Nastavení proměnné BASH_ENV umožňuje spustit „před-skript“ při spouštění skriptu.
1 Cyklus se opakuje, dokud testovací příkazy skončí úspěchem (s kódem 0).
2 Text je (po provedení rozvoje) testován proti jednomu vzorku za druhým. Je-li nalezen vzorek, se kterým se text shoduje, provedou se příkazy příslušné větve a zbytek vzorků se netestuje. Ukončovač větve „;;“ je možno umístit na konec příkazu nebo na samostatnou řádku. Tip: části vzorků můžete podle potřeby odzvláštnit běžnými způsoby (zpětným lomítkem, dvojitými uvozovkami apod.).
3 Pokud není seznam „parametry“ prázdný, vypíše uživateli (na standardní chybový výstup) číslované menu, přečte od něj odpověď a text vybraného parametru uloží do proměnné. Uživatel může požádat o znovuvypsání menu stisknutím „Enter“ bez odpovědi. Zadá-li uživatel neplatnou odpověď, do proměnné se uloží prázdný řetězec. Cyklus se opakuje, dokud není seznam parametrů prázdný (ale je obvyklé ho přerušit příkazem „break“).
4 Poznámka: hranice funkce či skriptu příkaz „exit“ neomezují, hranice podprostředí však ano — „exit“ dokáže ukončit podprostředí a vrátit návratovou hodnotu z něj, ne však ukončit obalující interpet.
5 Tato hodnota může být negována metapříkazem „!“, viz kapitolu Metapříkazy.
6 Poznámka: jednoduchý příkaz toto pole přepíše jednoprvkovým polem; obsah tohoto pole není nikdy ovlivněn metapříkazem „!“.
7 Poznámka: roury se dvěma a více členy, příkazy spuštěné na pozadí operátorem „&“ a příkazy dosazené operátorem $() ponechávají této zvláštní proměnné její hodnotu (tzn. nezmění ji).
8 Pozor na skutečnost, že formát čísla u proměnné „EPOCHREALTIME“ závisí na nastaveném národním prostředí. K získání zpracovatelného formátu lze použít podprostředí s nastavením „LC_ALL=C“.
9 Jde o číslo řádku, na které se nachází první znak jednotlivého příkazu; pokud příkaz pokračuje přes několik řádků, hodnota proměnné LINENO je na všech těchto řádcích jednotná.
10 První variantu lze použít pouze pro parametry $0 až $9. Druhou variantu (tu se složenými závorkami) lze použít i pro ostatní parametry (např. ${10}) a lze ji skombinovat s pokročilými formami dosazení.
11 Dvojité uvozovky zde znamenají, že pro správnou funkci musí být $@ (resp. $*) uvedeny uvnitř dvojitých uvozovek.
12 Výchozí N je 1. Pokud je pozičních parametrů méně než N, příkaz selže s návratovým kódem 1 a poziční parametry zůstanou nezměněné.
13 Parametry lze nastavit pouze najednou, přičemž se všechny stávající parametry (kromě $0) ztratí.
14 Závorku „{“ můžete oddělit na samostatnou řádku, pokud vám to připadne přehlednější; je-li uzavírací závorka „}“ na stejné řádce jako poslední příkaz těla funkce, musí být od něj oddělená středníkem nebo „&“.
15 Exportované funkce se uloží do zvláštních proměnných prostředí a další instance bashe, která je tímto způsobem „zdědí“, je opět načte jako funkce. Doporučuji exportování funkcí příliš nepoužívat, protože tak můžete snadno ztratit kontrolu nad tím, kam všude se vaše funkce dostanou.
16 Druhá a třetí varianta vyžadují, abyste měl/a k danému souboru právo „x“ (spouštění).
17 Pozor! Pokud cesta ke skriptu neobsahuje lomítko „/“, Bash bude skript hledat jako externí příkaz (tzn. s použitím proměnné PATH). Pro spuštění skriptu z aktuálního adresáře tedy před název souboru uveďte „./“!
18 Poznámka:
19 Poziční parametry jsou dostupné již v předskriptu a jejich případná změna se zachová do hlavního skriptu. To vám umožňuji použít předskript k „předzpracování“ pozičních parametrů.
20 Jde o číslo řádky, na které se nachází první znak jednotlivého příkazu; pokud příkaz pokračuje přes několik řádek, hodnota proměnné LINENO je na všech těchto řádkách jednotná.
21 Bash uvažuje řazení podle aktuálně platné lokalizace. Pro binární řazení použijte „LC_ALL=C“.
22 Název proměnné „regvyraz“ zde nemá žádný speciální význam, je zvolen jen pro přehlednost; můžete použít jakoukoliv proměnnou. Jednoduchý regulární výraz (takový, který neobsahuje mezery, uvozovky, zpětná lomítka ani hranaté závorky) můžete dokonce zadat přímo, pro zadání složitějšího výrazu je ale striktně doporučeno jej předem uložit do proměnné, protože pravidla odzvláštňování v tomto kontextu jsou složitá a neintuitivní.
23 Typicky se tento test používá k odhalení pevných odkazů nebo synonym vytvořených např. pomocí „mount --bind“.
Líbí se vám tento projekt a chcete, aby byl ještě lepší? Můžete mi s tím pomoci. Zmiňte se o něm technicky zdatným přátelům, opravte překlepy a nahlašte nefunkční zaklínadla, aby mohla být opravena; poskytněte mi zpětnou vazbu nebo se zapojte do vývoje nových kapitol. Další informace na GitHubu v dokumentu Jak se zapojit.
[BY-SA]

Veškerý obsah této stránky (text, obrázky, zdrojový kód) je možno upravovat a šířit pod podmínkami licence Creative Commons Attribution-ShareAlike 4.0 International. Upozorňuji, že uvedená licence vyžaduje uvedení seznamu autorů, licence a zdroje a poskytnutí stejné či kompatibilní licence k provedeným změnám, jsou-li nějaké. Příslušné údaje jsou dostupné na stránce „Přehled autorů“. Šíření obsahu bez těchto údajů nebo šíření upravené verze bez poskytnutí adekvátní licence k provedeným úpravám je pravděpodobně porušení licenčních podmínek a může být postihováno. Poskytování zdrojového kódu při šíření není touto licencí vyžadováno.

Pro nové verze, další informace, aktuální zdrojový kód a možnost se zapojit do projektu „Linux: Kniha kouzel“ navštivte jeho repozitář na GitHubu.