Published: 10. 12. 2010   Category: Programming

Skriptování v shellu. Na co si dát pozor?

1. Přenositelnost

Shellové skripty mohou být napsány tak, aby běžely na různých platformách. Pokud se zaměříme jenom na GNU/Linux, i zde narazíme na spoustu odlišných prostředí, Linux běží na nejrůznějších síťových zařízeních, fiber switchích, routerech, atd. Přenositelný skript by mohl běžet i na FreeBSD, HP-UXu, AIXu, Solarisu a podobně. Je potřeba dát pozor na několik věcí:

  1. Spoléhání se na hardwarové specifika daného OS. Např. v Linuxu máme disky označené /dev/sda, /dev/sdb, jinde zase /dev/c0t0d0, /dev/c0t1d0, atd...
  2. Přepínače programů se mohou lišit. Z tohoto hlediska jsou systémy založené GNU na často mnohem příjemnější, protože často obsahují volby navíc, které ulehčují práci. Přechodem na jiný systém můžeme zjistit, že utilitám chybí volby a přepínače, které známe z Linuxu a spoléháme se na ně.
  3. Cesty k programům se mohou lišit. I v rámci jednoho systému, se může stát, že program odladěný a dobře fungující v shellu po spuštění cronem nefunguje. Programátor se totiž může spoléhat na nějakou proměnou prostředí (např. $PATH), relativní cestu nebo nastavení daného shellu. Ve skriptu volané programy a cesty je proto dobré zadávat absolutně a abychom si ulehčili práci, je dobré všechny tyto cesty a programy zadat do proměnných. Tím pádem, pak IFCONFIG=/sbin/ifconfig definovaný někde na začátku skriptu pak při volání $IFCONFIG nebo ${IFCONFIG} pustí vždy, co potřebujeme. Při přenosu na systém, kde se program nachází v /usr/sbin/ifconfig, pak stačí změnit cestu jen na jednom místě někde na začátku skriptu a ne ho celý opravovat a spoléhat se, že někde na něco nezapomeneme.

2. Globální proměnné

Skript může běžet v různých podmínkách/prostředích, např. skript odladíte, ale při spuštění cronem přestane fungovat. Často bývá problém ve spolehnutí se na nějakou proměnnou prostředí.

3. Eskejpovaní

Občas se člověk zapotí pokud by rád použil speciální znaky, jejichž zápis je nutné zahájit znakem zpětné lomítko \.

Příklad: Chci spustit skript na vzdáleném terminálu pomocí SSH. To se provede jednoduše příkazem:

ssh uživatel@server "příkaz"

Kamenem úrazu jsou zde příkazy v "uvozovkách", které kde se různé proměnné ovšem rozvinou nejprve v lokálním shellu, a teprve pak se provedou na vzdáleném stroji.

$ ssh bruchano@vzdaleny.stroj.cz "echo $HOME"
/home/bruxy
$ ssh bruchano@vzdaleny.stroj.cz "echo \$HOME"
/home/bruchano
$ ssh bruchano@vzdaleny.stroj.cz 'echo $HOME'
/home/bruchano

4. Práce s řetězci

Při práci s řetězci se vyplatí hojně využívat uvozovky. Zkuste si následující příklad:

STRING="Feb  9            Syslog"
$ echo $STRING 
Feb 9 Syslog
$ echo "$STRING"
Feb  9            Syslog

Bez uvozovek shell zruší mezery navíc. Jednou jsem psal skript pro zpracování logů, který přestal začátkem měsíce fungovat. Problém byl v tom, že pokud den byl 1. až. 9., pak za zkratkou měsíce byly dvě mezery, jinak pouze jedna ("Feb 10" vs. "Feb  9"). Porovnávání řetězce bez uvozovek tak způsobilo, že co mělo být shodné nebylo a blbá chyba byla rázem na světě.

V těchto i dalších případech se vyplatí při ladění spouštět skript voláním interpretru: bash -x ./mujskript, zde bash před každým voláním vypíše spouštěný příkaz. Díky tomu je vidět obsah proměnných, a lehce tak dohledáme, proč v n-té iteraci nějakého cyklu skript vypovídá poslušnost.

5. Pozor na bashová rozšíření

Bash je natolik populární, že některá rozšíření bereme jako samozřejmost. Jedná se zejména o práci s poli a sekvence. Při přenosu skriptu na POSIXový shell, pak zjistíme, že nefunguje {1..5} nebo chybí příkaz seq a jiné.

6. Blbu-vzdornost

Blbu-vzornost je vlastnost, která pomůže uchránit uživatele skriptu, před tím, aby si jeho použitím ublížil. Vyžaduje skript zadat parametr při spuštění? Jak se zachová pokud ho pustíme bez parametru nebo s nějakou neočekávanou hodnotou? Vypíše chybovou hlášku a korektně se ukončí nebo vesele zasyflí systém?

Dokonce i sám programátor, který je zároveň uživatelem vlastního skriptu je na místě napsat skript blbu-vzdorně. Zejména, pokud skript spouštíme třeba jednou za měsíc, lehce během té doby zapomeneme jak jsme to vlastně se vstupem a spouštěním mysleli, musíme se znovu vrtat ve zdrojáku, vzpomínat co a jak, atd.

7. Výkon

Především v případě, že skript zpracovává řádově „hodně“ dat je dobré se při jeho tvoření zamyslet nad výkonem. Jestli skript běží 10 vteřin nebo 20 je mnohdy celkem jedno, ale 10 nebo 20 minut už je znát. Někdy je dobré se rozmyslet hned na začátku, zda by nebylo lepší využít jiný jazyk, ať už interpretovaný a nebo rovnou kompilovaný. Výkonnost se oproti shellu často zvýší mnohonásobně. Ovšem u kompilovaných jazyků většinou bývá zase vývoj časově náročnější.

Čím se dají zlepšit parametry výkonu?

  1. Omezení volání externích programů, vytváření subshellů a rour.
    cat soubor.txt | grep slovo
    grep slovo soubor.txt
    
  2. Využívání programu sed místo vestavěných funkcí. Např. náhrada všech podřetězců v $A, je ${A//co/čím}, atd.
  3. Následující skript je pomalejší
    A=$( echo "234.432*823.1" | bc)
    B=$( echo "123.321*823.1" | bc)
    
    než pokud by se zapsal takto:
    read A B <<< $( bc <<< "234.432*823.1;123.321*823.1")
    
  4. Opakovaně používané mezivýsledky ukládat do souboru. Např. při hledání souborů podle nějakého klíče, které pak dále zpracováváme, se vyplatí tyto soubory vyhledat jednou, uložit si jejich cesty a názvy do souboru a pak dalšímu skriptu předkládat data v souboru místo mnohonásobného volání příkazu find a čekání na diskové operace. (Pokud je volání mezi jednotlivými findy delší než udržení výsledků v diskové keši, je to znát.)