OOB: Objektumok bash parancsértelmezőben

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:

  1. o_<objektum>:   egy konkrét objektum,
  2. o_<objektum>_@:   az objektum összes szerepe,
  3. o_<objektum>@:   az objektum és az összes szerepe,
  4. o_@:   az összes objektum és szerep, kivéve a gyökérobjektumot,
  5. o_<objektum>_+:   az objektum közvetlen szerepei,
  6. o_<objektum>+:   az objektum és a közvetlen szerepei,
  7. o_+:   minden felső szintű objektum,
  8. o_<minta>-:   a mintára előlről illeszkedő objektumnevek,
  9. o_<objektum>_-:   az objektum közvetlen szerepei,
  10. 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 +x

Futtassuk 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 +x

Az 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
1 2 3 4 5 6 7 8 9