Hrátky z řádky: tenký led - skripty generují skripty

21. 4. 2008
Doba čtení: 3 minuty

Sdílet

Opět se setkáváme u pravidelné pondělní dávky tipů a triků z černé řádky. Ve většině stávajících dílů jsme se snažili omezit rizika chyb v shellových skriptech. Dnes zmíníme možnosti, jak si naopak efektivně na problém náhodnými překlepy naběhnout. Skripty totiž samozřejmě mohou generovat skripty.

Spusť příkaz – eval

Jsou situace, kdy potřebujete „vypočítat“ název programu, který budete spouštět. (Například při hodně nebezpečné situaci, kdy uživatel zadává vašemu skriptu přímo nějakou výkonnou komponentu.) V některých verzích bashe navíc expanze proměnných neprobíhala v prvním slově příkazové řádky, takže název programu nešlo přímo brát z proměnné. Dnes už bash proměnné expanduje a název programu z proměnné brát můžete, dokonce i s předpřipra­venými počátečními argumenty:

moje_razeni="sort -nr"
$moje_razeni < in > out
# příklad je jen ilustrativní, konkrétně v tomhle případě by
# jistě bylo lepší zadefinovat a užít alias

V souladu s gramatikou bash ale expanduje proměnné až po částečném rozboru příkazové řádky, kdy jsou identifikovány jednotlivé příkazy oddělené středníkem, rourou ap. Když tedy výsledek „výpočtu“ programů (nebo ten vstup od uživatele), který chcete spustit, zahrnuje složitejší konstrukce, nezbývá, než použít explicitní prosbu o vyhodnocení, tj. eval:

moje_razeni="sort -n | tac"
# neznám parametr -r, znám jen tac, opak catu
cat in | $moje_razeni
# nezabere! bash zkouší spustit sort s parametry "-n", "|" a "tac"
cat in | eval $moje_razeni
# zabere, eval obsah proměnné $moje_razeni nechá vyhodnotit na místě,
# tj. stejně, jako kdybyste rovnou napsali
cat in | sort -n | tac

Samozřejmě použití evalu zesiluje vaši povinnost myslet na správné uvozovkování a ochraňování speciálních znaků. Plejáda problémů, na něž můžete narazit, je příliš široká, takže mohu poradit jediné: postupujte po malých krůčcích a testujte každý krok a eval izolovaně, než se přesvědčíte, že na požadovaných vstupech dělá, co má.

Spusť skript v aktuálním shellu – source, tečka

Už dříve jsme vyráběli jednoduché skripty. Ty se spouštěly v samostatném procesu (tj. ekvivalent „/bin/bash nazev_skriptu“), takže nemohly např. nastavit adresář nebo proměnné prostředí tak, aby nastavení zůstalo platné pro náš shell. Pak jsme zmínili aliasy jako prostředek pro spuštění předpřipravené konstrukce v aktuálním shellu.

Kombinaci obojího představuje spuštění skriptu v aktuálním shellu, tzv. sourcnutí. Provedeme jej pomocí příkazu „source“, který se pro zpestření a z historických důvodů dá zkráceně také zapsat jako „.“ (tečka). Nejspíš proto, aby se vám v manuálových stránkách hůře hledalo, co ta tečka dělá…

source muj_skript
# stejně jako
. muj_skript
# spustí muj_skript přímo v aktuálním shellu

Kdy sourcování potřebujete? Kdykoli má skript ovlivnit vaše aktuální prostředí. (Nastavení adresáře, proměnných, aliasů… V tomto smyslu je např. ~/.bashrc také „sourcován“.) Jaká jsou rizika? Velká, sourcnutý skript může omylem poničit vaše (exportované i neexportované) proměnné, zanechat vás v adresáři, se kterým nepočítáte, nebo vám rovnou uzavřít spojení! (Rozhodně neradím napsat „exit“ do vašeho .bashrc.)

Jsem spuštěn, nebo sourcnut?

Právě konfigurační skripty, kterými si má uživatel nastavit nějaké proměnné ve svém prostředí ap., potřebují nutně, aby byly načteny přímo aktuálním bashem, nikoli spuštěny. Vhodné je uživatele o této časté chybě informovat. O detekci se postará následující magická formulka:

if [ -z $BASH_ARGV ]; then
    cat >&2 <<-KONEC
    Tento inicializační skript musíte pro správný běh spustit takto:
      . $0
    tj. v aktuálním shellu a nikoli jako samostatný proces.
    KONEC
    # pozor na to, aby před slovem KONEC byly jen tabulátory, ne mezery
    exit 1
fi

Generuj-kopíruj-vlož

Dnešní celkem stručný díl uzavřu svým oblíbeným tipem pro spouštění mnoha příkazů s různými argumenty najednou. Program xargs v našem seriálu určitě spatříte. Je dobrý, když jste si jisti a chcete všechny příkazy hned spustit (nebo ručně každý potvrzovat, použijete-li „ xargs -p“). Občas se ale hodí hromadu příkazů automaticky připravit a pak spouštět víceméně naráz, jen s lehkou optickou kontrolou, jestli nedělají blbosti, nebo s lehkými pauzami, aby se nějaký systém nezahltil.

V takovém případě často vyrobím např. jednoduchý for-cyklus, který zamýšlené příkazy vypíše na konzoli. Prohlédnu si je a zkusím první zkopírovat a hned vložit, čímž se příkaz spustí. Pokud jsem s výsledkem spokojen, zkopíruju dalších pár příkazů a zas je vložím (možná do jiné konzole, aby mi tahle neodrolovávala). A když vidím, že systém dávku vstřebal, vezmu další.

bitcoin školení listopad 24

Když je příkazů prostě moc na ruční copy-paste přes konzoli, použiju generovací for cyklus, ale přesměruji jej do souboru:

(for args in ...; do echo cmd $args; done) > rychloskript.sh

Rychloskript si pak prohlédnu, případně tu a tam vložím „ sleep 5“, aby se prostředí mohlo vzpamatovat, a nakonec spustím „ sh rychloskript.sh“ nebo „ source rychloskript.sh“, podle toho, co potřebuji.

Autor článku