Hlavní navigace

Drobnosti ze shellového zápisníku (1)

8. 4. 2002
Doba čtení: 7 minut

Sdílet

Když jsem začal pracovat s příkazovým řádkem, psal jsem si na každou často prováděnou akci vlastní skript. Tak se shromáždilo značné množství dvouřádkových skriptů pro různé drobné úkony. Časem jsem zapomněl, k čemu který slouží, a proto vznikl shellový zápisník, plný užitečných příkazů. O některé z nich se s vámi dnes rozdělím. Doufám, že najdete inspiraci, jak si svou práci usnadnit…

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=ig­noreboth
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>_<čís­lice>.

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

root_podpora

# 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).

Byl pro vás článek přínosný?