Listák
Szűrők
Az OOB parancsok általában az argumentumaikban megkapott objektumokkal végeznek műveleteket. A szükséges objektumok neveit előállíthatjuk szűrőkkel is, sőt ez a módszer biztonságosabb, mint a szűrők mellőzése, ugyanis a szűrő:
- szintaktikai ellenőrzést végez és
- csak a létező objektumneveket adja vissza.
A szűrők objektumok neveiből és kiegészítő jelekből állhatnak. Mindössze három kiegészítő jel használatával változatos szűrőket tudunk létrehozni. A következő mintákat valósítjuk meg:
- o_<objektum>: egy konkrét objektum,
- o_<objektum>_@: az objektum összes szerepe,
- o_<objektum>@: az objektum és az összes szerepe,
- o_@: az összes objektum és szerep, kivéve a gyökérobjektumot,
- o_<objektum>_+: az objektum közvetlen szerepei,
- o_<objektum>+: az objektum és a közvetlen szerepei,
- o_+: minden felső szintű objektum,
- o_<minta>-: a mintára előlről illeszkedő objektumnevek,
- o_<objektum>_-: az objektum közvetlen szerepei,
- o_-: minden felső szintű objektum.
A szűrők megvalósítása
A szűrőket megvalósító segédfüggvényt a basic.oob fájlba írjuk meg, amelynek verziószáma így 0.0.2 lesz. Írjuk be a fájl végére a következő függvényt:
oob_filter() { # input: object-pattern # result: oob_list ( array for name of object descriptors )
local dobj t val; dobj=d$1 t=${dobj%?} oob_list=()
[[ $dobj =~ ^do_[0-9a-zA-Z_]*[+@-]?$ ]] || { oob_trap "Pattern syntax error:" "$1"; return 1; }
case $dobj in # search for objects and roles
do_@) oob_list=( ${!do_*} );; # all objects and roles
*-) oob_list=( $(compgen -X $t*_* -v $t) );; # filter object to pattern without roles
*_@) eval oob_list=( \${!$t*} );; # all roles to the object
*_*@) val=${!t}; eval oob_list=( ${val:+$t} \${!${t}_@} );; # object and its all roles
*_+) oob_list=( $(compgen -X $t*_* -v $t) );; # direct roles to $i object
*_*+) val=${!t} oob_list=( ${val:+$t} $(compgen -X ${t}_*_* -v ${t}_) );; # object and its direct roles
*) val=${!dobj} oob_list=( ${val:+$dobj} );; # one object with exact match with pattern
esac # search for objects and roles
} # oob_filter()A függvény először törli az oob_list tömb tartalmát. A második programsorban a kapott argumentumot egy regex (regular expression) kifejezéssel összeveti és eltérés esetén a hibaágat hajtja végre. Abban az esetben, ha a hibaágban nem fejezzük be a szkript futását, a hibaágból való visszatérés után a függvény a hívónak a vezérlést azonnal visszaadja. A visszatérési érték ilyenkor hibaérték (return 1). Ha az argumentum szintaktikája megfelelő volt, akkor egy kiválasztó utasítással (case ... esac) meghatározza a szűrő típusát és végrehajtja a szűrést. A szűrés eredményét az oob_list tömbbe gyűjti. A tömb elemei a megtalált objektumok leíróinak neve. A hívó ezt a tömböt használja a saját feladatai végrehajtásakor.
A szokásos megoldás az lett volna, hogy echo parancsokkal kiírjuk a neveket és a kimenetet irányítjuk változóba. Ennek a sémája ehhez hasonló:
parancs() { echo "szöveg"; } # a parancs megvalósítása függvénnyel
adat="$(parancs)" # a parancs kimenetének átirányítása változóba
Így a "szöveg" bekerül a változóba, amely neve a bash megkötéseivel bármi lehet. Ennek az az ára, hogy a parancsot egy bash alhéjban kell futtatni és annak betöltése plusz időbe kerül. Az alhéjban való futtatást azzal kerüljük el, hogy az oob_filter függvény közvetlenül beírja az adatot a tömbbe. A feleseges bonyolítás helyett előre meghatározzuk a tömb nevét (oob_list).
Mivel az oob_filter lesz a legtöbbet használt utasítás, ahol lehet, gyorsítjuk az objektumleírók keresését is. A 4. programsorban megvalósított szűrőtípus esetén egyszerűen lekérdezünk egy változót, amelyet a bash tölt ki. A ${!minta*} az összes olyan változónevet visszaadja, amelyik a minta karaktersorozattal kezdődik. Ez tehát a leggyorsabb keresés, mert ezt egyetlen értékadással meg tudtuk oldani.
A 10. programsorban megírt szűrőtípus azt használja ki, hogy csak azt kell ellenőrizni, hogy a megadott objektumváltozó létezik-e. Ennek nevét a dobj helyi változó tartalmazza. Az osztály számát (amely az objektum leírójának az értéke) a ${!dobj} kifejezés adja vissza, ahol a ! az indirekt hivatkozást jelzi, azaz, hogy a dobj változó valójában egy mutató a ténylegesen lekérdezendő változóra. Ha az létezik (és van tartalma), akkor értéket kap a val változó. A ${val:+$dobj} kifejezés a dobj változó értékét (tehát az objektumleíró nevét) csak akkor adja vissza, ha a val változó nem üres. Így két értékadó utasítással megkaptuk az eredményt. Ez lényegesen gyorsabb eljárás, mintha feltételvizsgálatot alkalmaztunk volna.
A 6. és 7. programsorokban használhatnánk az első szűrőtípus technikáját, azonban a minta nem eleve adott, hanem változó tartalmazza. Egymásba ágyazott indirekt változóneveket a bash nem tud kezelni, így az eval beépített utasítást használjuk, amely az argumentumait szövegként olvassa fel és parancsként végrehajtja. A bash a $t helyére beírja annak értékét, de a másik $ karaktert levédtük a \ jellel. Így az eval már egy ahhoz hasonló utasítást lát és hajt végre, mint amelyet az első szűrőtípusban megvalósítottunk.
A többi keresésben (5., 8. és 9. programsorok) a bash compgen beépített parancsát használjuk, amelynek az -X opcióval meg lehet adni az eredményből kizárandó nevek mintáját is. Igaz, a compgen parancsot alhéjban kell futtani azért, hogy az eredményt a kimeneti tömbbe irányítsuk, azonban ez még így is gyorsabb, mintha a kivételek ellenőrzését mi magunk írtuk volna meg ciklusba zárt feltételvizsgálattal. (A leggyorsabb utasítás a meg nem írt utasítás.)
Most már különböző kereséseket végezhetünk az objektumaink között. A próbához módosítjuk a develop.sh fájl ## run szakaszát:
## run
main() { oob_filter "$@"; declare -p oob_list; o_star_planet4_moon1_sayHello; }
: set -x; main "$@"; set +xFuttassuk a szkriptet argumentumként különböző kereséseket megadva!
A listák kiíratása
Az oob_list tömb az objektumleírókat tartalmazza, a felhasználók viszont az objektumnevekkel dolgoznak. Így az oob_filter segédfüggvényt nem is használjuk felhasználói szkriptekben, azt csak az OOB belső utasításai hívják meg. Ám a szűrőkre alapozva kényelmes felhasználói interfészt írhatunk az objektumok, az adattagok és a metódusok listázására. A listázó parancsot a basic.oob fájlba (verzió: 0.0.3) írjuk meg:
oob_ls() { # input: [-dm] object-patterns # displays name of objcts, data or methods
local i dobj mode all; local -a oob_list
[[ $1 == -?* ]] && mode=$1 && shift; [[ -z $* ]] && all="o_@" # list all objects and roles
for i in $all $@; do # one pattern
oob_filter $i && echo "$i:" || continue # get object descriptors to pattern
for dobj in ${oob_list[@]}; do # one object descriptor
echo " ${dobj#d}" # display object name
case $mode in *d*) local -n t=dc_${!dobj}_d; echo " data: ${!t[@]}";; esac
case $mode in *m*) local -n t=dc_${!dobj}_m; echo " methods: ${!t[@]}";; esac
done # one object descriptor
echo "" # empty line between patterns
done # one pattern
} # oob_ls()Ha a parancs első argumentuma kötőjellel kezdődik, akkor az opció. Ha az opció tartalmazza a d betűt, akkor kiírja az adattagok neveit is, ha az m betűt tartlamazza, akkor a metódusokét is. A parancsot meghíva az csoportosítva kiírja az egyes szűrőkhöz tartozó objektumneveket és opcionálisan az objektumokhoz tartozó adattagok és metódusok neveit. Ha nem adunk meg mintát, akkor az összes objektumot megkeresi. A mintákhoz egyesével meghívja az oob_filter parancsot és a visszaadott objektumleíró listákat feldolgozva objektumonként kiírja a kért adatokat. Az objektumleírók nevéből a #d operátorral tudjuk eltávolítani a kezdő d karaktert.
Ha olyan új oob_trapExec parancsot írunk, amelyik nem fejezi be a szkript futását, akkor hiba esetén a hibás mintát átlépi és a következő mintával folytatja a végrehajtást.
Az ellenőrzéshez és a kísérletezéshez átírjuk a develop.sh fájl ## run szakaszát:
## run
main() { oob_ls "$@"; o_star_planet4_moon1_sayHello; }
: set -x; main "$@"; set +xAz oob_ls parancsnak megírjuk a rövidebb változatát is. Ez nem fogad el opciót, csak a minták felsorolását. Az eredmény a mintákhoz tartozó összes létező objektumnév az oob_LSS tömbben, de mindegyik név csak egyszer fordul elő. Ha nem adunk meg mintát, a kimeneti tömb üres lesz. A rövid listázó parancsot a basic.oob (verzió: 0.0.4) írjuk meg:
oob_lss() { # input: object-patterns # oob_lss ( name of objects )
local i all in=""; local -a oob_list allList; local -A uniq; oob_LSS=()
for i; do oob_filter $i && allList+=( ${oob_list[@]} ); done # collect object descriptors
for i in ${allList[@]}; do eval oob_LSS+=( \${i${uniq[$i]}#d} ); uniq+=( [$i]=n ); done # make list uniq
} # oob_lss()A parancs mindegyik mintához meghívja az oob_filter utasítást és annak sikeres lefutása esetén egy tömbbe fűzi az összes objektumleíró nevét. Ha mindegyik mintát feldolgozta, akkor az objektumváltozók neveit csak akkor teszi a kimeneti tömbbe és távolítja el a kezdő d karaktert, ha az nem szerepel a uniq helyi asszociatív tömbben. Végül beírja az objektumváltozó nevét indexként a uniq tömbbe és az n karaktert adja értéknek.
A kimeneti tömbbe írt szöveg tartalmának megváltoztatásához megint az eval belső utasítást csapjuk be. A \${i${uniq[$i]}#d} kifejezést az eval ${i#d} kifejezésnek látja, ha a uniq tömbben nem szerepel a $i index. Egyébként meg ${in#d} kifejezésnek látja, amelynek nincsen értéke, hiszen ezt a legelső sorban definiáltuk.
Az ellenőrzéshez és a kísérletezéshez átírjuk a develop.sh fájl ## run szakaszát:
## run
main() { oob_lss "$@"; declare -p oob_LSS; o_star_planet4_moon1_sayHello; }
: set -x; main "$@"; set +x