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

Expanze jmen souborů

Poslední expanzí před odstraněním uvozovek je expanze jmen souborů (anglicky pathname expansion nebo file globbing). Shell v každém slově hledá žolíky *, ? a [, a pokud je najde, nahradí slovo abecedně setříděným seznamem jmen souborů, které vzoru odpovídají.

žolíkexpanze
*0 nebo více libovolných znaků
?jeden libovolný znak
[chars]jeden ze znaků v řetezci chars
[c1-c2]jeden ze znaků v rozsahu c1-c2, např. [0-9], [a-z]
[!chars]jakýkoliv znak, který není v chars

Rozsahy a výčty lze kombinovat, takže [a-zA-Z0-9_] je jeden alfanumerický znak nebo podtržítko.

> ls
a.txt  b.txt  foto1.jpg  foto2.jpg  poznamky.md
> echo *.txt
a.txt b.txt
> echo foto?.jpg
foto1.jpg foto2.jpg
> echo [ab].txt
a.txt b.txt
> echo [!ab]*
foto1.jpg foto2.jpg poznamky.md

Zápis {word1,word2} uváděný v některých přehledech žolíků sem nepatří — je to expanze závorek, která probíhá v jiné fázi a nekontroluje existenci souborů.

Žolíky nejsou regulární výrazy

Symboly vypadají podobně jako v regulárních výrazech, ale znamenají něco jiného. Ve vzoru shellu znamená * „libovolné znaky“, kdežto v regulárním výrazu znamená „opakování předchozího prvku“. Vzor *.txt má tedy jako regulární výraz zapsaný pro grep tvar .*\.txt.

Tento rozdíl je pravidelným zdrojem chyb, protože v jednom skriptu se obojí běžně vyskytuje vedle sebe: grep a sed pracují s regulárními výrazy, case, expanze parametrů a expanze jmen souborů se žolíky.

Tečka na začátku a lomítko

Žolíky mají dvě výjimky.

  • * ani ? neodpovídají tečce na začátku jména souboru, takže skryté soubory se implicitně nezpracují;
  • žádný žolík neodpovídá lomítku, takže * nikdy nepřekročí hranici adresáře.
> ls -a
.  ..  .skryty  a.txt
> echo *
a.txt
> echo .*
. .. .skryty

Zápis .* je tedy nebezpečný, protože zahrnuje . a ... Chceme-li skryté soubory bez těchto dvou položek, použijeme .[!.]* nebo přepínač dotglob zmíněný níže.

Když se vzor neshoduje s ničím

Ve výchozím nastavení shell vzor, ke kterému neexistuje žádný soubor, nechá beze změny. To je častý zdroj chyb ve skriptech.

> cd /tmp/prazdny
> echo *.txt
*.txt
> for f in *.txt; do echo "zpracovavam ${f}"; done
zpracovavam *.txt

Cyklus tedy proběhl jednou s neexistujícím „souborem“ jménem *.txt. Obranou je buď test existence uvnitř cyklu,

for f in *.txt
do
    [ -e "${f}" ] || continue
    echo "zpracovavam ${f}"
done

nebo změna chování shellu pomocí shopt:

shopt -s nullglob    # neshodujici se vzor zmizi (cyklus neprobehne ani jednou)
shopt -s failglob    # neshodujici se vzor je chyba

Další volby shopt

Příkaz shopt zapíná a vypíná vlastnosti shellu podobně jako set (viz podkapitola set); volby týkající se expanze jmen souborů jsou tyto.

volbavýznam
nullglobneshodující se vzor se odstraní
failglobneshodující se vzor způsobí chybu
dotglob* zahrne i soubory začínající tečkou
nocaseglobignoruje velikost písmen
globstarzpřístupní vzor **
extglobzapne rozšířené vzory (viz níže)
> shopt -s nocaseglob
> echo *.JPG
foto1.jpg foto2.jpg
> shopt -u nocaseglob

Volba globstar zpřístupní vzor **, který na rozdíl od * prochází i do podadresářů:

> shopt -s globstar
> echo **/*.txt
a.txt doc/b.txt doc/hlubsi/c.txt

V jednodušších případech mi lze použít jako pohodlnou náhradu za find.

Znakové třídy POSIX

Uvnitř hranatých závorek lze místo výčtu použít pojmenované třídy znaků. Ty jsou čitelnější a fungují správně i pro jiné jazyky než angličtinu.

třídavýznam
[[:alpha:]]písmeno
[[:digit:]]číslice
[[:alnum:]]písmeno nebo číslice
[[:space:]]bílý znak
[[:upper:]], [[:lower:]]velké, malé písmeno
[[:punct:]]interpunkce
> echo foo[[:digit:]][[:digit:]][[:digit:]][[:digit:]].txt
foo0001.txt foo0002.txt [...]

Všimněme si dvojích hranatých závorek: vnější patří žolíku, vnitřní jsou částí zápisu třídy.

Rozšířené vzory

Po shopt -s extglob jsou k dispozici konstrukce, které se blíží možnostem regulárních výrazů. seznam je jeden vzor nebo více vzorů oddělených |.

vzorvýznam
?(seznam)nula nebo jeden výskyt
*(seznam)nula nebo více výskytů
+(seznam)jeden nebo více výskytů
@(seznam)právě jeden ze vzorů
!(seznam)cokoliv, co neodpovídá žádnému ze vzorů
> shopt -s extglob
> echo @(a|b).txt
a.txt b.txt
> echo !(*.txt)          # vse krome textovych souboru
foto1.jpg foto2.jpg poznamky.md

Tytéž vzory lze použít i v konstrukci case a v expanzi parametrů.

Potlačení expanze

Expanzi potlačíme uvozovkami, apostrofy nebo zpětným lomítkem. To je nutné například tehdy, když chceme žolík předat jinému programu, aby jej zpracoval sám.

> find . -name "*.txt"

Bez uvozovek by shell vzor *.txt expandoval ještě před spuštěním find, a ten by dostal jména souborů z aktuálního adresáře, nikoliv vzor. Ve chvíli, kdy takový příkaz funguje v jednom adresáři a v jiném ne, je příčinou obvykle právě tohle.