Ukázalo se, že shellový zápisník je v porovnání s mnoha skripty dobrá myšlenka, zvlášť na různé neobvyklé činnosti – pokud jsem náhodou nezapomněl jméno skriptu, pak jsem si téměř určitě nepamatoval pořadí argumentů, které je mu nutno předat. Naproti tomu skripty v zápisníku žádné argumenty nemají, protože se všechny parametry vkládají editací příkazu.
V zápisníku řadím příkazy do tématických celků, s krátkými komentáři, které vysvětlují, k čemu byl příkaz užitečný. V článku uvádím také vysvětlení jeho funkce.
Nejdříve stručně popíši způsob použití zápisníku:
Když zapnu počítač, spustím svůj oblíbený editor, a jedním z trvale otevřených souborů je právě shellový zápisník. Chci-li vyvolat nějaký příkaz, pokusím se jej najít v historii shellu (pro Bash je to Control-r řetězec, a pak tisknout Control-r, dokud nenajdu to, co potřebuji). Pokud uspěji, vyedituji řádek a spustím příkaz. Pokud ne, nahlédnu do zápisníku, zkopíruji blok a vložím jej na příkazovou řádku (obyčejně prostředním tlačítkem myši).
Trik 1: Dlouhá historie
Abychom měli co nejvíce příkazů v historii, je vhodné ji prodloužit. Aby historie z jedné konzole (nebo virtuálního terminálu) nepřepsala historii z druhé, paralelně otevřené, použijeme připojení historie. To zařídíme tak, že do některého startovacího příkazu (např. /etc/profile nebo ~/.profile) napíšeme například (platí pro Bash, čísla můžeme měnit dle libosti):
shopt -s histappend
HISTFILESIZE=20000
HISTSIZE=5000
HISTCONTROL=ignoreboth
export HISTFILESIZE HISTSIZE HISTCONTROL
Dále musíme zajistit, aby se tento příkaz vykonal vždy při inicializaci – virtuální terminály spouštíme s volbou „Použít jako přihlašovací shell“, su používáme pouze s volbou -l (může se lišit podle distribuce).
Trik 2: Komentáře na řádce
Komentovat si můžeme nejen skripty, ale i samostatný příkazový řádek. Zadáme si např.:
ls -alR ~/tmp/ /tmp/ /var/tmp/ # kuk
Příkaz se provede stejně jako neokomentovaný, a budeme-li později hledat v historii řetězec „kuk“, najdeme příkaz rychleji, než kdybychom hledali „tmp“. Jediné omezení spočívá v tom, že Control-r zatím odmítá české znaky.
Trik 3: Středník
Skripty většinou píšeme na několik řádků. Naproti tomu Control-r vyvolává z historie vždy jen jeden řádek. Je proto dobré na něj umístit vše, co se spouští při určité akci. Příklad:
cat * >všechno.txt.new ; mv všechno.txt.new všechno.txt
Jedinou výjimkou jsou příkazy do, then a else, za které středník napsat nesmíme.
Trik 4: Ladění s příkazem echo
Pokud ladíme nějaký potenciálně destruktivní příkaz, můžeme k tomu použít příkaz echo – místo, aby se příkaz vykonal, pouze se vypíše. Ale pozor! Příkaz echo nestačí na „zpacifikování“ přesměrovacích operátorů < a >. K nim je třeba předřadit ještě zpětné lomítko, tedy např. \>. Příklad:
for i in * ; do echo cat $i \>\>soubor ; done
Pokud jsme si jisti se správností, příkaz echo (a zpětná lomítka před přesměrováním) odstraníme. Druhou možností je nakopírovat výstup uvedeného příkladu a vložit jej zpět jako příkaz, třetí možností je přesměrování rourou do shellu:
( for i in * ; do echo cat $i \>\>soubor ; done ) | sh
(v tomto příkladu závorky nutné nejsou, ale obecně nutné být mohou)
Pro úplnost ještě dodám, že tato metoda nezachovává uvozovky. Lze to však napravit (viz trik 5).
Příkazy cp, mv a rm lze též „zabezpečit“ volbou -i. Ta zajistí, že všechny destruktivní kroky budeme muset potvrdit. Volbu si můžeme dát i do svého inicializačního souboru:
alias cp=‚cp -i‘
alias mv=‚mv -i‘
alias rm=‚rm -i‘
Pokud jsme si příkazem jisti, volbu -i lze neutralizovat volbou -f.
Trik 5: Uvozovky v akci
Před psaním jakéhokoliv složitějšího příkazu je dobré přečíst si tu část dokumentace, která popisuje postup expanze v shellu. Nejčastěji se setkáváme s expanzí uvozovek:
Apostrofy
Prvním typem uvozovek jsou apostrofy. Vše, co je uvnitř apostrofů, se interpretuje jako jeden argument tak, jak je napsáno, s výjimkou znaku apostrof – mezi apostrofy jej nemůžeme napsat. Potřebujeme-li jej, použijeme buď zápis ‚\'' nebo '„‘“' (tedy opustíme režim apostrofů, napíšeme apostrof a opět se vrátíme do režimu apostrofů).
Uvozovky
Interpretace uvozovek je mnohem zajímavější. Vše co je uvnitř, se interpretuje jako jeden argument (s výjimkou „$@“ ověřit). Ovšem znaky jako $, \ nebo ! a samozřejmě " svůj speciální význam neztrácejí. Pokud je chceme napsat, jednoduše před ně předřadíme \. Naopak \ sekvence, které nemají pro shell význam, zůstanou bez expanze (tedy např. \1 zůstane \1, ale \$ se převede na $).
utx:~$ PSU=3 ; A=„$PSU psi“ ; B=‚$PSU psi‘ ; C=„$PSU\$PSU“
utx:~$ echo „A=‚$A‘ B=‚$B‘ C=‚$C‘“ ; echo ‚B=‘\‚$B\'''
A=‘3 psi' B=‚$PSU psi‘ C=‚3$PSU‘
B=‚$PSU psi‘
utx:~$ echo „\1\“\‚\$„
\1“\‘$
Důležité je, že ve většině moderních interpretrů může být součástí řetězce též znak konce řádku.
Obrácenou funkci, než mají uvozovky, má příkaz eval. Ten znovu expanduje již expandovaný příkaz. Více se o expanzi dočtete v dokumentaci k příkazovému interpretru Bash.
utx:~$ B=‚$PSU psi‘ ; echo $B ; eval echo $B
$PSU psi
3 psi
Shellový zápisník
A nyní už můžeme přistoupit k vlastnímu zápisníku. Připomínám, že pracuji v interpretru Bash. V jiném interpretru příklady mohou, ale nemusí fungovat. Jednoduchá expanze je ve většině interpretrů stejná, ale některé složitější konstrukce nikoliv.
Protože Root vychází v HTML a šířka sloupce je omezená, delší příkazy se na obrazovce přelomí. Pokud si je však z prohlížeče vykopírujete, zlomy zůstanou jen na správných místech (neplatí to však pro konzolové prohlížeče).
Příklady zde uvedené nechť vám poslouží za inspiraci při řešení podobných problémů…
Opakované akce se soubory a adresáři
Jde o nejjednodušší z možností použití shellu. Příkazem, vhodným před všemi těmito operacemi je (pro Bash):
shopt -s nullglob
Ten zajistí, že pokud žádný soubor odpovídající vzoru neexistuje, výsledkem expanze bude prázdný seznam.
Archivace
# rozbalit všechny archivy v adresáři
for i in *.tar.gz *.tgz *.tar.Z ; do tar -z -x -f „$i“ ; done
# pozor – existuje mnoho verzí podpory pro bzip2: -I, -y aj.
for i in *.tar.bz2 ; do tar -I -x -f „$i“ ; done
for i in *.zip ; do unzip „$i“ ; done
# atd.
Při vyhodnocení argumentů lze též vyvolat jiný příkaz (např. date):
# zabalit všechny adresáře v aktuálním adresáři a archivy opatřit datem
for i in * ; do [ -d „$i“ ] && tar -z -c -f „$i-$(date -r "$i“ +%Y%m%d).tar.gz" ; done
# totéž v notaci historického Bourne Shellu
for i in * ; do if test -d „$i“ ; then tar -z -c -f „$i- date -r \"$i\" +%Y%m%d
.tar.gz“ ; fi ; done
Reorganizace a přejmenování souborů
Reorganizace souborů je poměrně častá akce. Psát k jejímu provedení skript se nám nevyplatí, neboť se zpravidla použije jen jednou. K akci poslouží shellové vzory, externí programy nebo regulární výrazy.
Použití vzorů shellu pro vytvoření nového jména souboru
Moderní shelly (např. Bash) mají možnost úprav řetězců přímo, bez volání externích programů.
Mějme plný adresář obrázků ve formátu TIF a chceme je převést na formát JPEG. Ze seriálu o grafice již víme, že to umí convert. Bylo by však velmi nepohodlné převádět stovku souborů ručně. Napíšeme si tedy cyklus:
# převést všechny soubory TIFF v aktuálním adresáři na JPEG
for i in *.tif ; do convert „$i“ „${i%.tif}.jpg“ ; done
Použití externího programu pro vytvoření nového jména souboru
Nejstarší shelly žádnou záměnu řetězců neměly. Pokud chceme mít skript maximálně přenositelný, poslouží nám externí program basename. Místo nového zápisu „$(“výraz")" použijeme starší zápis. Pokud je uvnitř uvozovek, má jiný průběh expanze, a tak musíme psát „ \"výraz\"
“.
# převést všechny soubory TIFF v aktuálním adresáři na JPEG (stará notace)
for i in *.tif ; do echo convert „$i“ „ basename \"$i\" .tif
.jpg“ ; done
Ne každý soubor TIFF je vhodný ke konverzi do formátu JPEG. Abychom poznali vhodný TIFF, budeme analyzovat výstup prigramu tiffinfo a použijeme podmínku. Potřebujeme analyzovat několik řádků najednou, proto programem tr převedeme znaky konce řádků. Několik rour za sebou vytvoří kolonu. Podmínka pro if se nastaví podle výsledku posledního příkazu v koloně – vyhledávání regulárního výrazu. Výsledek pak může vypadat takto:
# převést vybrané soubory TIFF v aktuálním adresáři na JPEG
for i in .tif ; do if tiffinfo 2>/dev/null „$i“ | tr ‚\n‘ § | grep -qv „\(Photometric Interpretation: palette color\|Bits/Sample: 1.Photometric Interpretation: [a-z]-is-.)“ ; then convert „$i“ „${i%.tif}.jpg“ ; else echo „Soubor $i není správného typu“ ; fi ; done
Použití regulárního výrazu pro vytvoření nového jména souboru
Pro zpracování regulárních výrazů voláme externí program sed. Nejdříve si předvedeme, jak bude vypadat zmíněný převod souborů TIFF na JPEG.
# převést všechny soubory TIFF v aktuálním adresáři na JPEG (s příkazem sed)
for i in *.tif ; do convert „$i“ „$(echo "$i“ | sed ‚s/\.tif/.jpg/‘)" ; done
V dalším příkladu předpokládejme, že máme soubory obr0001.jpg, pes13.jpg apod. a budeme je chtít přejmenovat na obr_0001.jpg, pes13.jpg:
# přejmenování – vložit podtržítko před čísla
for i in *.jpg ; do mv -i „$i“ „$(echo "$i“ | sed ‚s/\([a-z])\([0–9])/\1_\2/‘ )" ; done
Ti, co před časem pozorně četli seriál o regulárních výrazech, již jistě poznají, že skript zamění každou sekvenci <písmeno><číslice> za sekvenci <písmeno>_<číslice>.
A teď něco složitějšího: Předpokládejme, že máme soubory obr1.jpg až obr2000.jpg, sken01.jpg až sken100.jpg a chceme je přejmenovat tak, aby číselný údaj byl vždy čtyřmístný.
# přejmenování – čísla budou čtyřmístná
for i in *.jpg ; do mv -i „$i“ „$(echo "$i“ | sed ‚:1;s/\([^0–9])\([0–9]..[^0–9])/\10\2/;t1‘ )" ; done
Podobně si můžeme vyrobit příkaz, který vytváři trojmístná čísla (bude se měnit počet teček uprostřed).