Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Dělení slov a IFS

Dělení slov je fáze, ve které shell rozdělí výsledky předchozích expanzí na jednotlivá slova, tedy na jednotlivé argumenty spouštěného příkazu. Je to zdaleka nejčastější zdroj nesrozumitelného chování skriptů, a proto jí věnujeme samostatnou podkapitolu.

Platí dvě zásadní pravidla:

  1. Dělení slov se aplikuje pouze na výsledky expanzí — na expanzi parametrů, aritmetickou expanzi a substituci příkazů. Text, který jsme ve skriptu napsali přímo, se nedělí (ten je na slova rozdělen dříve, při parsování řádku).
  2. Dělení slov se neprovádí uvnitř uvozovek ani apostrofů.

Proměnná IFS

Které znaky jsou oddělovači, určuje proměnná IFS (internal field separator). Výchozí hodnotou je mezera, tabulátor a znak nového řádku.

Chování se liší podle toho, jestli je oddělovačem bílý znak, nebo ne:

  • bílé znaky (mezera, tabulátor, nový řádek) v IFS: posloupnost několika oddělovačů za sebou se chová jako jeden a oddělovače na začátku a na konci se ignorují;
  • ostatní znaky: každý výskyt odděluje, takže dva za sebou vytvoří prázdné slovo.

Rozdíl je vidět na následujícím příkladu.

> FOO="a   b"
> printf '[%s]\n' ${FOO}
[a]
[b]
> IFS=:
> BAR="a::b"
> printf '[%s]\n' ${BAR}
[a]
[]
[b]

Právě proto se PATH prochází dvojtečkou jako oddělovačem korektně i tehdy, když některá položka chybí.

Nastavíme-li IFS na prázdný řetězec, dělení slov se neprovede vůbec a z expanze vznikne jediné slovo.

> IFS=
> FOO="a b c"
> printf '[%s]\n' ${FOO}
[a b c]

Znaková expanze

Do IFS často potřebujeme dostat znak, který se špatně zapisuje, typicky nový řádek nebo tabulátor. Slouží k tomu zápis $'…' (tzv. ANSI-C quoting):

IFS=$'\n'         # deleni pouze na radky
IFS=$'\t'         # deleni pouze tabulatorem
IFS=$' \t\n'      # vychozi hodnota

Uvnitř $'…' se interpretují sekvence známé z jazyka C: \n, \t, \\, \', \xHH a další. Podrobněji viz Uvozování.

Měníme-li IFS, je slušné si původní hodnotu uložit a po skončení práce ji obnovit, jak jsme to dělali v kapitole Cykly. Alternativou je nastavit IFS jen pro jeden příkaz zápisem IFS=: prikaz, který proměnnou změní pouze v prostředí tohoto příkazu.

> IFS=: read -r a b c <<< "prvni:druhy:treti"
> echo ${b}
druhy

Proč uvozovky kolem expanzí

Nyní můžeme přesně vysvětlit doporučení, které v textu opakovaně zaznívá. Uvažujme soubor, jehož jméno obsahuje mezeru.

> SOUBOR="muj soubor.txt"
> ls ${SOUBOR}
ls: cannot access 'muj': No such file or directory
ls: cannot access 'soubor.txt': No such file or directory
> ls "${SOUBOR}"
muj soubor.txt

Bez uvozovek se hodnota po expanzi rozdělila na dvě slova, a ls tak dostal dva argumenty místo jednoho. S uvozovkami se dělení slov neprovede.

Druhý, méně nápadný případ nastane, když je proměnná prázdná. Bez uvozovek zmizí argument úplně; s uvozovkami zůstane prázdný argument.

> PRAZDNA=""
> printf '[%s]\n' ${PRAZDNA}
> printf '[%s]\n' "${PRAZDNA}"
[]

To je důvod, proč se v kapitole Větvení píše [ -n "${x}" ] a ne [ -n ${x} ]: v druhém případě by test dostal jediný argument -n, což vyhodnotí jako test neprázdnosti řetězce -n, tedy vždy pravdivě.

$* a $@

Rozdíl mezi $* a $@, popsaný v kapitole Vstupní argumenty, je nyní jen důsledkem pravidel výše. Předpokládejme skript spuštěný jako ./script.sh "prvni argument" druhy.

zápisvýsledek
$*jedno slovo, rozdělené dělením slov → prvni, argument, druhy
$@jednotlivé argumenty, ale rozdělené dělením slov → prvni, argument, druhy
"$*"jediné slovo prvni argument druhy (spojené prvním znakem IFS)
"$@"tři slova: prvni argument, druhy — beze změny
echo "--- \$* bez uvozovek"
for p in $*
do
    echo "[${p}]"
done

echo "--- \"\$@\" v uvozovkach"
for p in "$@"
do
    echo "[${p}]"
done
> ./script.sh "prvni argument" druhy
--- $* bez uvozovek
[prvni]
[argument]
[druhy]
--- "$@" v uvozovkach
[prvni argument]
[druhy]

Prakticky to znamená: argumenty předáváme dál vždy jako "$@". Totéž platí pro pole, kde "${pole[@]}" zachová prvky tak, jak byly uloženy (viz Pole).

Kdy uvozovky vynechat

Uvozovky vynecháváme jen tehdy, když dělení slov opravdu chceme — typicky když je v proměnné seznam položek, který má vzniknout více argumentů:

VOLBY="-l -a"
ls ${VOLBY}          # zamerne bez uvozovek: dve volby

I tady je ovšem čistším řešením pole:

VOLBY=(-l -a)
ls "${VOLBY[@]}"