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ík | expanze |
|---|---|
* | 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.
| volba | význam |
|---|---|
nullglob | neshodující se vzor se odstraní |
failglob | neshodující se vzor způsobí chybu |
dotglob | * zahrne i soubory začínající tečkou |
nocaseglob | ignoruje velikost písmen |
globstar | zpřístupní vzor ** |
extglob | zapne 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řída | vý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 |.
| vzor | vý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.