Linux: Kniha kouzel, 2.15 (1. března 2025)
Veškerá moc příkazové řádky/příkazového řádku přehledně, pro začátečníky i pokročilé

4.3. Bash / Vstup, výstup a přesměrování

Vývoj vanilkové příchuti Linuxu: Knihy kouzel byl 1. března 2025 ukončen. Tento text je zachován jako historický, ale chyby již nejsou opravovány. Odnože projektu pod kompatibilní licencí jsou vítány.

1. Úvod

Tato kapitola pokrývá nástroje interpretu Bash k ovládání vstupů a výstupů spouštěných příkazů i vstupů a výstupů samotného interpretu. Rovněž pokrývá nástroje ke čtení textových řetězců ze souborů, terminálu či výstupu spoustěného programu a nástroje k zápisu textových řetězců do souboru, na terminál, do souboru nebo na vstup spouštěného programu.

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

2. Definice

  • Deskriptor je (nejen v Bashi, ale pro každý jednotlivý proces v systému) číslovaný vstup nebo výstup. Základní deskriptory jsou „standardní vstup“ (číslo 0, „stdin“, budu zkracovat jako „s.vstup“), „standardní výstup“ (číslo 1, „stdout“, budu zkracovat jako „s.výstup“) a „standardní chybový výstup“ (číslo 2, „stderr“, budu zkracovat jako „s.ch. výstup“). Deskriptory 3 až 9 jsou určeny pro libovolné použití, deskriptory 10 až 255 pro vnitřní použití interpretem.
  • Vstup je deskriptor, ze kterého může proces číst data.
  • Výstup je deskriptor, do kterého může proces zapisovat data.
  • Roura („pipeline“, v některých příručkách také nazývaná „kolona“) je spojení dvou nebo více jednoduchých příkazů operátorem „|“. Bash pak tyto příkazy spustí paralelně a připojí standardní výstup příkazu nalevo od | na standardní vstup příkazu napravo od |. Díky tomu pak data „protékají“ přímo z jednoho procesu do druhého. Návratovým kódem roury je ve výchozím nastavení návratový kód posledního uvedeného jednoduchého příkazu.
  • Přesměrování deskriptorů (či jen přesměrování) je úkon, při kterém Bash něco provede s deskriptory vznikajícího procesu (nebo svými vlastními). Přesměrování se provádí jedno po druhém zleva doprava, jak jsou zadána na příkazové řádce.

Užitečná poznámka ke spouštění příkazů: kdykoliv z Bashe spustíte jakýkoliv nový proces, ten nejprve zdědí všechny deskriptory od interpretu (ne jen ty tři základní), pak pro něj Bash provede přesměrování deskriptorů specifikovaná na příkazovém řádku (včetně propojení procesů rour) a pak se teprve pokusí program spustit. To znamená, že vedlejší účinky přesměrování (např. vytvoření souborů) se projeví i v případě, že se Bashi program spustit nepodaří (např. proto, že program takového názvu neexistuje).

3. Zaklínadla

3/1 Roura

@přesměrovat s.výstup příkazu A na s.vstup příkazu B#1 (1)
příkaz-A včetně přesměrování [|[&] příkaz-B včetně přesměrování]
@totéž, ale příkaz B přitom neuzavřít do podprostředí#2
příkaz-B včetně přesměrování < <(příkaz-A včetně přesměrování)

3/2 Přesměrování vstupu (čtení odněkud)

@čtení z existujícího souboru (obecně/příklad)#1
[deskriptor]< cesta
< "../můj soubor.txt"
@čtení ze zadaného textu (obecně/příklady)#2 (2)
[deskriptor]<<< parametr
sort <<< $'zoo\nahoj\nseminář'
sort <<< "A$(echo BC)D"
@čtení z prázdného vstupu#3
[deskriptor]< /dev/null
@čtení ze s.výstupu bloku příkazů (obecně/příklad)#4
[deskriptor]< <(příkazy...)
sort < <(echo Zoo; echo Abeceda)
@zduplikovat/přečíslovat deskriptor pro čtení#5
[cílový-deskriptor]<&zdrojový-deskriptor
[cílový-deskriptor]<&zdrojový-deskriptor-
@zduplikovat pojmenovaný deskriptor pro čtení#6
příkaz a parametry [cílový-deskriptor]<&$identifikator-pojm-desk
@čtení z bloku řádků#7 (3)
[deskriptor]<< 'ukončovač'
řádky textu
ukončovač
@zavřít deskriptor#8
deskriptor<&-
@čtení z bloku řádků po rozvoji#9 (4)
[deskriptor]<<- identifikátor
řádky textu
identifikátor

3/3 Přesměrování výstupu (zápis někam)

@zápis do souboru; existuje-li: zkrátit na prázdný/připojit za konec#1 (5)
[deskriptor]>[|] cesta
[deskriptor]>> cesta
@zápis nikam#2
[deskriptor]> /dev/null
@zápis na s.vstup bloku příkazů (obecně/příklad)#3
[deskriptor]> >(příkaz)
df -h > >(sed -u 1q; sort)
@zduplikovat/přečíslovat deskriptor pro zápis#4
[cílový-deskriptor]>&zdrojový-deskriptor
[cílový-deskriptor]>&zdrojový-deskriptor-
@zduplikovat pojmenovaný deskriptor pro zápis#5
příkaz a parametry [cílový-deskriptor]>&$identifikator-pojm-desk
@zavřít deskriptor#6
deskriptor>&-
@přepis souboru (čtení i zápis, bez zkrácení) (obecně/příklad příkazu)#7 (6)
cílový-deskriptor<>cesta
echo ABC 2>&1 1<> můj-soubor.txt

3/4 Některá obvyklá přesměrování

@zahodit s.výstup i s.ch. výstup (alternativy)#1
&> /dev/null
>/dev/null 2>/dev/null
@s.ch. výstup nasměrovat na s.výstup (alternativy)#2
2>&1
2> /dev/stdout
@s.výstup nasměrovat na s.ch. výstup (alternativy)#3
> /dev/stderr
>&2
@s.výstup a s.ch. výstup připojit za konec souboru (pokud neexistuje, vytvořit prázdný)(alternativy)#4
>> cesta 2>&1
&>> cesta

3/5 Pojmenované deskriptory

@otevřít (obecně/příklad)#1 (7)
exec {identifikator}režim cesta/k/souboru
exec {mujdesk}< můj-soubor.txt
@zavřít pro vstup/pro výstup/pro přepis#2
exec {identifikator}<&-
exec {identifikator}>&-
exec {identifikator}<&- {identifikator}>&-
@příklad použití#3
exec {mujd}>>můj-log.txt
date "+%F %T" >&$mujd
printf 'Skript spuštěn (deskriptor %d)\n' "$mujd" >&$mujd
exec {mujd}>&-

3/6 Ostatní aplikace přesměrování

@aplikovat přesměrování na skupinu příkazů#1 (8)
{
příkazy
} přesměrování
@aplikovat přesměrování permanentně (na všechny následující příkazy)#2
exec přesměrování

4. Zaklínadla: Čtení a zápis z Bashe

4/1 Zápis (výstup)

@formátovaný výstup#1
printf [--] 'formátovací řetězec' [parametry] [přesměrování]
@zapsat text#2
printf %s[\\n] "text" [přesměrování]
@zapsat bajty#3 (9)
printf '\xAB[\xCD]' [přesměrování]
@zapsat „\0“ (nulový bajt)#4
printf \\0 [přesměrování]

4/2 Čtení (vstup)

Před použitím zaklínadel z této podsekce si prosím přečtěte podsekci „Jak funguje příkaz read“!

@přečíst do proměnné záznam ukončený znakem „\n“/„\0“/zadaným ASCII znakem#1
IFS= read -r[u deskriptor] promenna
IFS= read -r[u deskriptor] -d "" promenna
IFS= read -r[u deskriptor] -d "ASCII-znak" promenna
@přečíst do proměnné N znaků/1 znak#2
IFS= read -r[u deskriptor] -N N promenna
IFS= read -r[u deskriptor] -N 1 promenna
@načíst záznam a rozložit ho do pole#3
IFS="oddělovače" read -r[u deskriptor] [-d "ASCII-ukončovač-záznamu"] -a pole
IFS=":" read -r -a data < /etc/passwd && printf %s\\n "${data[4]}"
@načíst záznam a rozložit ho do proměnných (obecně/příklad)#4
IFS="oddělovače" read -r[u deskriptor] [-d "ASCII-ukončovač-záznamu"] promenna
IFS=":" read -r a b c d e < /etc/passwd
@načíst všechny záznamy do nového pole (alternativy)#5
readarray -t [-d "ukončovač"] [-s kolik-z-přeskočit] [-n kolik-max-načíst] [-u deskriptor] pole
IFS="ukončovače" read -r[u deskriptor] -d "" -a pole || true
@načíst všechny záznamy do existujícího pole#6 (10)
readarray -t -O index [-d "ukončovač"] [-s kolik-z-přeskočit] [-n kolik-max-načíst] [-u deskriptor] pole
@načíst celý zbytek vstupu do proměnné#7
IFS= read -rd "" promenna < <(tr -d \\0)

4/3 Interakce s uživatelem (jen při čtení z terminálu)

@načíst heslo#1 (11)
[if] IFS= read -rs [-p "Text výzvy:"] promenna && echo [then ... fi]
@nabídnout uživateli řádku k úpravě (obecně/příklad)#2 (12)
IFS= [TMOUT=[časový-limit-sek]] read -r [-p "Text výzvy"] -ei "Výchozí text řádky" promenna

4/4 Je deskriptor připojený na terminál?

@je s.vstup připojený na terminál?#1
test -t 0
@je s.výstup/s.ch. výstup připojený na terminál?#2
test -t 1
test -t 2
@je deskriptor N připojený na terminál?#3
test -t N

4/5 Nastavení Bashe související s deskriptory a rourami

@návratový kód vícenásobné roury se vezme: z prvního příkazu, který selhal/vždy z posledního příkazu roury (výchozí nastavení)#1
set -o pipefail
set +o pipefail
@zkrácení existujícího souboru obyčejným přesměrováním výstupu (zakázat/povolit)#2
set -C
set +C

5. Další poznámky

5/1 Použití přesměrování deskriptorů

Přesměrování deskriptorů se obvykle uvádějí na konec příkazu, za poslední parametr; např. takto:

printf %s\\n "Chyba!" >&2

Ale Bash je dovoluje uvést kamkoliv, dokonce i před příkaz nebo mezi parametry, např.:

>&2 LC_ALL=C sort <<< $'P\nC\nA' -

Přesměrování je možno aplikovat i na celý blok příkazů, na cykly (např. „while“) apod.

Zvláštní pozornost je potřeba věnovat použití „bloku řádků“ jako vstupu; definice ukončovače se totiž uvádí za značku <<, ale číst vstup začne Bash až od následující řádky, což umožňuje tento druh vstupu skombinovat např. s rourou:

LC_ALL=C sort << "KONEC" | tr A _
ZAHRADA
Abeceda
KONEC

5/2 Jak funguje příkaz read

IFS="oddělovače" read -r[u deskriptor] [-d "ukončovač"] promenna
IFS="oddělovače" read -r[u deskriptor] [-d "ukončovač"] -a pole
read -r[u deskriptor] -N počet-znaků promenna
  • Před zahájením čtení „read“ do všech uvedených proměnných přiřadí prázdný řetězec. (Jde-li o čtení do pole, pole se vyprázdní.)
  • Čte ze zadaného deskriptoru (výchozí je s.vstup, tedy 0) znak po znaku a přidává je na konec první zadané proměnné (resp. prvního prvku pole).
  • Když na vstupu narazí na oddělovač (kterýkoliv znak z hodnoty proměnné IFS), přeskočí na další proměnnou/další prvek pole, s výjimkou případu, kdy se oddělovač nachází bezprostředně před ukončovačem záznamu; v takovém případu je oddělovač ignorován. Do poslední zadané proměnné se pak načte celý zbytek záznamu (tam už je hodnota proměnné IFS ignorována).
  • Když „read“ na vstupu narazí na ukončovač záznamu (výchozí je „\n“), úspěšně tím skončí čtení (tzn. návratový kód 0).
  • Když „read“ narazí na konec vstupu (nebo hardwarovou chybu), skončí čtení s návratovým kódem 1. Již načtené znaky budou v proměnných ponechány.

Postřehy:

  • Ne-ASCII znaky lze použít v proměnné IFS (tzn. jako oddělovače záznamů), ale ne v parametru „-d“ (tzn. nemohou sloužit jako ukončovače záznamů).
  • Nulový bajt lze použít jako ukončovač záznamu (tvar parametru je pak „-d ""“), ale ne jako oddělovač záznamů (nelze ho uložit do proměnné IFS).
  • V případě, že „read“ narazí na konec vstupu, skončí s návratovým kódem 1, ale již přečtené znaky ponechá v příslušných proměnných.
  • Nastavíte-li časový limit, v případě jeho vypršení skončí „read“ s kódem 142 a načítané proměnné budou vyprázdněny.

5/3 Jak funguje příkaz printf

printf [-v promenna] [--] 'formátovací řetězec' [parametr]

Ve formátovacím řetězci jsou interpretovány formátovací značky (uvozené znakem „%“) a lomítkové sekvence (začínající zpětným lomítkem „\“), oba případy lze odzvláštnit zdvojením (tzn. „%%“ se interpretuje jako obyčejný znak % a „\\“ jako obyčejné zpětné lomítko). Netriviální formátovací řetězce doporučuji uzavřít do apostrofů, aby nedocházelo ke konfliktům se zvláštním významem některých znaků na příkazovém řádku.

Algoritmus příkazu printf:

  • 1. Vzít formátovací řetězec a nahradit v něm všechny lomítkové sekvence odpovídajícími znaky či řetězci.
  • 2. Projít všechny formátovací značky ve formátovacím řetězci zleva doprava; pro každou načíst jeden parametr z parametrů za formátovacím řetězcem (pokud chybí, použít místo něj prázdný řetězec), zformátovat podle značky a dosadit místo ní.
  • 3. Výsledný řetězec vypsat na standardní výstup.
  • 4. Pokud zbyl alespoň jeden parametr, vzít řetězec, který byl výstupem kroku 1 a jít na krok 2 se zbylými parametry.

V případě použití parametru „-v“ se řetězce místo vypsání ukládají do zadané proměnné a nesmí obsahovat nulový bajt (jinak může být chování nepředvídatelné).

6. 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.

7. Tipy a zkušenosti

  • Při práci s deskriptory je třeba mít na paměti, že „pozice“ čtení či zápisu není vlastností samotného deskriptoru. Otevřením souboru přesměrováním deskriptoru vznikne nová „čtecí pozice“ i v případě, že stejný soubor je již otevřen přes jiný deskriptor; pokud ale stávající deskriptor zduplikujete nebo ho zdědí nový proces, nová pozice nevznikne a čtení z obou deskriptorů bude posouvat tutéž čtecí pozici (existující pravděpodobně někde v jádře).

7/1 Časté chyby s rourou

Pozor na implicitní vznik podprostředí v některých situacích! Bash automaticky obklopí podprostředím každý jednoduchý příkaz roury a také i jednoduchý příkaz spouštěný na pozadí nebo v operátoru „$()“. To znamená, že např. tento blok kódu vypíše „19“, protože přiřazení z konstrukce „:=“ zůstalo izolované v podprostředí:

unset a
true "${a:=12}" &
wait $!
printf %s\\n "${a:=19}"

Nejčastěji se tato chyba vyskytuje ve formě pokusu o použití příkazu „read“ s rourou:

unset a
printf 99\\n | IFS= read -r a
printf %s\\n "$a"

V uvedeném příkladu zůstane hodnota „a“ nedefinovaná, protože Bash uzavře příkaz „read“ do samostatného podprostředí.

7/2 Problémy s nulovým bajtem

Bash obecně neumí pracovat s nulovým bajtem „\0“; umí ho však vygenerovat (příkazem printf) a přečíst jako ukončovač záznamu (příkazy read a readarray) a rovněž může být předáván rourou z příkazu do příkazu (což probíhá mimo Bash, ten rouru jen vytváří). Celkově je třeba při jakémkoliv pokusu o prácí s nulovým bajtem nástroji Bashe dát velký pozor, zejména ho nelze uložit do proměnných ani použít uvnitř textu parametru jakéhokoliv příkazu.

8. Další zdroje informací

9. Zákulisí kapitoly

V této verzi kapitoly chybí:

  • koprocesy
  • více „dalších zdrojů informací“
  • podrobnější výklad o „printf“

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

  • nic
1 Varianta „|&“ přesměruje navíc i s.ch. výstup.
2 Poznámka: Bash za konec zadaného textu vždy připojí znak „\n“, i v případě, že už tam je!
3 Zadaný ukončovač musí být neprázdný řetězec. Bash na přesměrovávaný vstup zapíše všechny řádky počínaje prvním řádkem po aktuálním příkazu a konče poslední řádkou, která se přesně neshoduje s ukončovačem (není dovolena odlišnost ani v bílých znacích na začátku či konci řádky!).
4 Pozor! Při použití tohoto zaklínadla nesmí být žádný znak v identifikátoru odzvláštněn (tzn. používejte jen znaky dovolené v názvu proměnné). Bash na přesměrovávaný vstup zapíše všechny řádky počínaje prvním řádkem po aktuálním příkazu a konče poslední řádkou, která se přesně neshoduje se zadaným identifikátorem (shoda musí být přesná, není dovolena odlišnost ani v bílých znacích na začátku či konci). Před použitím Bash celý blok řádek intepretuje, přičemž za zvláštní považuje pouze znaky „$“, „\“ a „`“ (s výjimkou apostrofů s dolarem, ty tam přímo použít nelze), můžete tam tedy dosazovat hodnoty proměnných a výstupy příkazů.
5 Varianta „>|“ dovolí přepsání existujícího souboru i v případě, že je nastavena volba „noclobber“.
6 Vyžaduje právo soubor číst i do něj zapisovat; neexistující soubor bude vytvořen, existující soubor nebude zkrácen a zápis začne bajt po bajtu přepisovat jeho obsah od začátku souboru.
7 „Režim“ může být pro čtení „<“, pro zápis „>“, „>|“, „>>“ a pro přepis „<>“ s významem jako u odpovídajícího přesměrování.
8 Složené závorky můžete umístit i na stejný řádek jako příkazy uvnitř, ale v takovém případě je musíte oddělit mezerami a poslední příkaz musí být ukončen operátorem „;“ nebo „&“! Proto doporučuji raději oddělovat složené závorky od příkazů koncem řádku.
9 AB a CD reprezentují dvoumístný hexadecimální zápis bajtů k zapsání
10 „index“ je index v poli, kam má začít příkaz zapisovat. Pro zápis od začátku pole uveďte index „0“.
11 Parametr „-s“ potlačí výstup psaných znaků na terminál. Při načítání hesel si dejte velký pozor na to, aby použitá proměnná nebyla exportovaná, jinak se totiž heslo objeví v prostředí nově spuštěných procesů, odkud může být šikovnými hackery odposlechnuto.
12 Pozor! V případě selhání příkazu (např. vypršení časového limitu) zůstane v cílové proměnné prázdný řetězec, ať už uživatel mezitím nějaké úpravy provedl nebo ne.
[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.