Hogyan kezeljük rugalmasan shell szkriptjeinkben a kapott paramétereket

botond küldte be 2019. 09. 11., sze – 15:25 időpontban

Tartalom

 

Bevezető

Programozásnál lényeges dolog, hogy ha megírunk egy bizonyos feladatot elvégző programot, akkor a kódunk legyen felkészítve többféle eshetőségre, variációra és lehetőség szerint paraméterezhető is legyen – amivel finomhangolhatóvá tehetjük a működését. Nincs ez másképpen a shell szkriptek esetében sem. A mai példában megnézzük, hogyan tudjuk rugalmasan kezelni szkriptjeinkben a kapott paramétereket, aminek segítségével mások számára is könnyebben használható programokat készíthetünk.

 

 

A program felépítése

Létrehozás

Hozzunk létre egy tetszőleges fájlt, pl.:

touch parameterek_rugalmas_feldolgozasa

Majd adjunk rá futtatási jogot:

chmod +x parameterek_rugalmas_feldolgozasa

Ezután jöhetnek a szerkesztgetések:

nano parameterek_rugalmas_feldolgozasa

És ha majd kerül bele tartalom, akkor időnként a program kipróbálása pl. egy másik terminálban:

./ nano parameterek_rugalmas_feldolgozasa

Kezdhetjük is.

Az alapok

Először állítsuk össze a programunk alapját, amelyben elhelyezzük a kapcsolóink számára szükséges asszociatív tömböt, és néhány egyéb alapbeállítást.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/bin/bash
# ------------------------------------------------------------------------------
# Linuxportál példaprogram: Paraméterek rugalmas feldolgozása
# ------------------------------------------------------------------------------
 
 
# ----------------------------------------------------------
# Kapcsolók/paraméterek alapértelmezett értékei
# ----------------------------------------------------------
declare -A PARAMS
PARAMS=(
    [DEBUG]=""                                              # Debug mód
    [HELP]=""                                               # súgó megjelenítése
    [VERSION]=""                                            # Verzió információk
    [EGYEB1]=""                                             # Egyéb paraméter #1
    [EGYEB2]=""                                             # Egyéb paraméter #2
    [EGYEB3]=""                                             # Egyéb paraméter #3
    [EGYEB4]=""                                             # Egyéb paraméter #4
)
 
 
# ----------------------------------------------------------
# További beállítások
# ----------------------------------------------------------
tabs 4                                                      # Kimenetben megjelenített tab-ok karakter mérete.
shopt -s nocasematch                                        # Kis- és nagybetűk figyelmen kívül hagyása a case szerkezetekben
green=$(tput setaf 2)                                       # Zöld kiemelőszín
red=$(tput setaf 1)                                         # Piros kiemelőszín
cyan=$(tput setaf 6)                                        # Cián kiemelőszín
reset=$(tput sgr0)                                          # Színek alapállapotba állítása
escape_value="***"                                          # "Menekülési érték", ha nincs megadva valamelyik kötelező érték
 
VERSION="1.0"                                               # Program verziója

Itt először deklaráljuk a PARAMS nevű asszociatív tömbünket, majd létrehozzuk benne a programunkban használt paraméterek számára a tároló elemeket, amiket alapértelmezett értékekkel töltünk fel (jelen esetben üres karakterláncokkal). Ezek a  tömb elemek fogják tárolni a program futása során a kapott kapcsolókat, amiket utána kényelmesen elérhetünk. A Bash nem teszi szükségessé a tömbökben lévő elemek előre deklarálását, azonban érdemes megtenni már csak azért is, hogy a fájlt később megnyitva egyből láthassuk hogy milyen változókat/címkéket használtunk a programunkban. Így utólag is könnyebben átlátjuk a kódunkat, ha esetleg fél év múlva módosítanunk kell rajta.

Nem muszáj asszociatív tömböt használni ilyen célra, elég lenne csak sima változókban tárolni a kapott argumentumokat, azonban így egységesebb a dolog, nem keverjük össze őket a program többi változóival (pláne, ha például később egy másik szkriptet kell be source-olnunk ebbe a fájlba), valamint egy tömböt könnyebben be tudunk járni szükség esetén egyetlen ciklussal is, minthogy "szétszórt" változókban keresgélni az értékeket. A tömbök kezeléséről ebben a leírásban találhatunk bővebb információkat.

Ezután a További beállítások soraiban elhelyezünk pár értékadást:

  • tabs 4: Beállítjuk, hogy a programunk kimenetében a tab-okat a Bash mindig 4 karakter szélességben tegye ki.
  • shopt -s nocasematch: Ezen shopt parancs segítségével beállítjuk, hogy a Bash ne különböztesse meg a program futása során a case elágazások kiértékeléseiben lévő kis- és nagybetűket. Ezáltal jelentősen csökkentjük a hibalehetőségek számát, mert így mindegy hogy kis- vagy nagybetűvel adják meg a programunknak a paramétereket.
  • green, red, cyan, reset: Szín beállítások a tput parancs használatával. Ezek segítségével különböző színekkel emelhetjük ki a kimeneteinket. Erről a Hogyan használjunk színeket a terminálban című leírásban találhatunk részletes információt.
  • escape_value: Ebben a változóban kell beállítanunk egy saját "menekülési értékünket" – amit biztosan nem fogunk kapni normál használat során a programban. Ennek célja, hogy a lentebbi Paraméterek előfeldolgozása részben az üres értékek helyére behelyettesítve a lenti részekben meg tudjuk különböztetni a kapcsoló teljes hiányát az értékek hiányától, hogy a kötelezően várt értékek elmaradása esetén hibát tudjunk generálni.
  • VERSION: Itt pedig csak úgy adunk egy verziószámot a programunknak, amit majd a megfelelő kapcsolókkal le is tudunk kérdezni.

Függvénytár kialakítása

Szükségünk van olyan kódrészekre is, amiket újra fel tudunk használni programunk futása során. Ilyen például a cím kiírása, a súgó megjelenítése, a verzió kiírása, stb. Ezeket a részeket függvényekben helyezzük el, hogy a programunkban bárhonnan meg tudjuk őket hívni. Ehhez kialakítunk egy függvénytár részt, ahol a függvények szerepelnek. Így a forráskód könnyebben karbantartható marad. Kódunkat tehát a függvényekkel folytatjuk:

36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# ----------------------------------------------------------
# Függvénytár
# ----------------------------------------------------------
 
# Cím kirakó függvény
function print_title() {
    echo "Linuxportál példaprogram - Paraméterek rugalmas feldolgozása"
}
 
# Verzió kirakó függvény
function print_version() {
    echo "Verzió: $VERSION"
}
 
# Súgó kirakó függvény
function print_help() {
cat << EOF
Használat: 
$(basename $0) [kapcsoló] [érték] ... vagy
$(basename $0) [kapcsoló]=[érték] ...
Kapcsolók:
  -h,       --help          Súgó megjelenítése
  -d,       --debug         Debug mód. Ha meg van adva, akkor kiírja a futás során a használt összes kapcsoló változót.
  -v,       --version       Verzió lekérdezése
  -e1,      --egyeb1        Egyéb 1 funkció futtatása
  -e2=x,    --egyeb2=x      Egyéb 2 tároló paraméter demonstrálása
  -e3,      --egyeb3        Egyéb 3 logikai tároló paraméter demonstrálása
  -e4=x,    --egyeb4=x      Egyéb 4 tároló paraméter demonstrálása
EOF
}
 
# "Kis" súgó kirakó függvény
function print_help2() {
    echo "Súgó megjelenítése: $(basename $0) -h vagy --help"
}

Itt tehát van négy függvényünk. Az első egyértelmű, ezek a címet és a program verzióját írják ki. A print_help() függvény pedig a súgót teszi ki. Itt a több soros és tabulált kimenet miatt a megszokott echo-k helyett egy heredoc-ban (Itt-dokumentum) tálaljuk a formázott kimenetet. így nem kell soronként bíbelődni az idézőjelekkel.

A súgóból az is kiderül, hogy a különböző funkciókat kétféle kapcsolóval is elérhetjük: rövid, illetve hosszú kapcsoló formában – mint ahogyan azt a legtöbb linuxos programnál is megszokhattuk. Továbbá az értékeket is kétféleképpen fogjuk feldolgozni, vagy egyenlőségjel megadással, vagy anélkül is elfogadja majd a programunk.

Itt még annyit érdemes megjegyezni, hogy ebben a formában a függvény blokkban nem alkalmazhatunk behúzást a heredoc-on, mert akkor hibát dob. Ha mégis tabulálni szeretnénk a sorokat – hogy függvényünk szebben nézzen ki – akkor kezdjük így a dokumentumot: "cat <<-EOF". Tehát a "balra csőr-ök" és a nyitó/záró szekvencia (jelen esetben EOF) közé tegyünk egy kötőjelet. Így már alkalmazhatunk behúzást a dokumentum soraira. Azonban ilyenkor a kimenetnél nem veszi figyelembe a sorok elején lévő tabulálásokat, csak az első karaktertől számítva kerül ki a tartalom (ami akár egy space is lehet). Tehát így macerásabb formázni a kimenetet. Ezt majd egy másik leírásban részletesebben átnézzük.

És végül a print_help2() függvényünk pedig csak egy praktikai célt szolgál: ha valahol hibát észlelünk a paraméterek feldolgozása során – például rosszul adta meg őket a felhasználó –, akkor a hibaüzenet után ezt a kis súgót tesszük ki. Ezzel jelezve, hogy hogyan jelenítheti meg a teljes súgót. Így a hibaüzenet nem kerüli el a figyelmet – szemben a fő súgóval, ami akár sok oldalas is lehet, emiatt a felhasználó könnyen szem elől tévesztheti a fontos hiba információkat.

 

 

Program indulása, paraméterek előfeldolgozása

Idáig elkészült minden fontos dolog, jöhet a program indulása. Ez igazából csak egy jelképes rész, itt írunk a kimenetre először, tehát itt történik érdemben valami elsőként:

73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# ----------------------------------------------------------
# Program indulás
# ----------------------------------------------------------
print_title                                                 # kiírjuk a címet. Ezt minden esetben kitesszük
 
 
# ----------------------------------------------------------
# Paraméterek előfeldolgozása
# ----------------------------------------------------------
 
# Nulla paraméter vizsgálata
if [[ $# -eq 0 ]] ; then                                    # Ha nem kapott paramétert a program, akkor 
    echo "${red}Hiba: Nincsenek paraméterek!${reset}" >&2   # Hibakimenetre írunk.
    print_help2                                             # Kis súgó kirakása
    exit 1                                                  # és kilépés 1-es hibakóddal
fi

A Paraméterek előfeldolgozása részben pedig elkezdjük az argumentumok vizsgálatát. Első körben megnézzük, hogy kapott-e a program kapcsolókat, paramétereket. Ha nem kapott egyet sem, akkor piros kiemeléssel kiírja a hibaüzenetet, amit a hibakimenetre (stderr) irányít, azután kiteszi az egy soros súgót, és kilép egy 1-es hibakóddal. Így nem is fut tovább a program.

A hibakimenetek és a kilépési kódok azért fontosak, mert ha például egy másik szkriptből futtatjuk a programot, akkor a kilépési kóddal tud kommunikálni a programunk az azt meghívó programmal. Például, ha itt 1-es hibakóddal kilépünk, akkor a futás után a hívó programban a $? változóban lehet lekérni ennek az eredményét, amit aztán fel lehet használni további elágazásokban, hibakezelésekben, stb. Így tehát ha ezeket betartjuk, akkor később bármikor egymásba ágyazva használhatjuk programjainkat, tudván, hogy a hibakódok visszakerülnek a futtató részekbe. Így akár egy nagyobb, automatizáló rendszer részeként is felhasználhatjuk jelenlegi forráskódunkat.

Ha tehát most lefuttatjuk az idáig elkészült programot kapcsolók nélkül, akkor ilyen kimenetet kapunk:

Pélaprogram - Paraméterek rugalmas feldolgozása - Paraméterek nélküli futtatás

Programunkat most egy while ciklussal folytatjuk, amiben elvégezzük a kapott paraméterek előfeldolgozását:

90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
REMAIN_PARAMETERS=()                                        # Maradék (nem várt) paraméterek tömbjének létrehozása
while [[ $# -gt 0 ]] ; do                                   # Ciklus indul, és megy, amíg maradt paraméter.
    parameter="$1"                                          # Soron következő első paraméter kiolvasása
    need_shift=0                                            # "shift szükséges" flag nullázása.
 
    # Egyenlőség jeles paraméterek kezelése
    if [[ $parameter = *"="* ]]; then                       # Ha a soron következő első paraméter tartalmaz "=" jelet, akkor
        key=${parameter%%=*}                                # a kulcs lesz az első egyenlőség jel előtti rész,
        value=${parameter#*=}                               # az érték pedig az első egyenlőség jel utáni rész.
    else                                                    # Ha a paraméterben nem volt egyenlőség jel, akkor
        key=$parameter                                      # a kulcs lesz maga a kapott paraméter
        value=$2                                            # és az utána lévő paraméter pedig lesz az esetleges érték.
        need_shift=1                                        # Itt jelezzük a lenti részeknek, hogy kell-e a plusz paraméter forgatás.
    fi
 
    # Kapcsolók elágazása
    case $key in                                            # case szerkezet: megvizsgáljuk a kulcsot, hogy mit tartalmaz.
        -h|--help)                                          # Súgó (logikai) kapcsoló érkezett...
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            PARAMS[HELP]=1                                  # Logikai érték beállítása az ennek fentartott tömb elemében.
        ;;
        -d|--debug)                                         # debug (logikai) kapcsoló ékezett...
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            PARAMS[DEBUG]=1                                 # Logikai érték beállítása az ennek fentartott tömb elemében.
        ;;
        -v|--version)                                       # verzió (logikai) kapcsoló érkezett...
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            PARAMS[VERSION]=1                               # Logikai érték beállítása az ennek fentartott tömb elemében.
        ;;
        -e1|--egyeb1)                                       # egyeb1 (logikai) kapcsoló érkezett...
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            PARAMS[EGYEB1]=1                                # Logikai érték beállítása az ennek fentartott tömb elemében.
        ;;
        -e2|--egyeb2)                                       # egyeb1 (értéket tároló) kapcsoló érkezett...
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            [[ "$value" == "" ]] && value=$escape_value     # Ha nem kapott értéket ez a kapcsoló, akkor beletesszük a menekülési értéket
            PARAMS[EGYEB2]=$value                           # a fentebb megállapított érték tárolása az ennek fentartott tömb elemében.
            [[ "$need_shift" == 1 ]] && shift               # Ha szükséges a paraméterek forgatása, akkor még egyszer forgatjuk.
        ;;
        -e3|--egyeb3)                                       # egyeb3 (logikai) kapcsoló érkezett...
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            PARAMS[EGYEB3]=1                                # Logikai érték beállítása az ennek fentartott tömb elemében.
        ;;
        -e4|--egyeb4)                                       # egyeb4 (értéket tároló) kapcsoló érkezett...
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            [[ "$value" == "" ]] && value=$escape_value     # Ha nem kapott értéket ez a kapcsoló, akkor beletesszük a menekülési értéket
            PARAMS[EGYEB4]=$value                           # a fentebb megállapított érték tárolása az ennek fentartott tömb elemében.
            [[ "$need_shift" == 1 ]] && shift               # Ha szükséges a paraméterek forgatása, akkor még egyszer forgatjuk.
        ;;
        *)                                                  # Ha ismeretlen paraméter jön a sorban, akkor
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            REMAIN_PARAMETERS+=("$key")                     # Felvesszük a kapott paramétert a maradék (nem várt) paraméterek tömbjébe.
        ;;
    esac                                                    # case vége
done                                                        # ciklus vége
# Idáig tehát "elfogyasztottuk" az összes paramétert a shift-ek által.
 
# Beállítjuk a megmaradt egyéb paramétereket a szabványos $1, $2, $3, stb változókba
# Így könnyebb a maradékot feldolgozni később, amennyiben szükséges.
set -- "${REMAIN_PARAMETERS[@]}"

Ennek a résznek az elején létrehozunk egy REMAIN_PARAMETERS nevű tömböt, amiben az előfeldolgozás során gyűjtjük a nem várt paramétereket, az esetleges későbbi feldolgozás céljából.

Ezután elindítunk egy while ciklust, ami addig tart, amíg el nem fogynak a program paraméterei. Ennek a logikája abból áll, hogy először megvizsgáljuk, hogy a soron következő első paraméter tartalmaz-e egyenlőségjelet. Ha van benne egyenlőségjel, akkor az mentén kettébontjuk a paramétert; az első része lesz a kulcs, majd az egyenlőségjel utáni része pedig az érték. Ebben az esetben egyetlen paraméterben kaptuk meg a kapcsolót és az értékét is, mivel nem választotta őket szét szóköz. Ilyenkor csak egyszer kell majd megforgatnunk az argumentumokat a shift paranccsal a lentebbi case részben, mivel csak egy paramétert használunk fel. A másik eset, amikor a kapott paraméter nem tartalmaz egyenlőségjelet. Ilyenkor vagy csak egy logikai kapcsolót kaptunk, aminek nem kell érték, hanem elég csak a jelenléte, vagy pedig az utána lévő paraméter lesz maga az értéke. Tehát itt egyszer mindenképpen kell forgatnunk a paramétereket, ami első körben "eltünteti" a kulcsot. És ha a kapcsolónak értéket is kell hordoznia, ami miatt felhasználjuk a következő paramétert is, akkor ebben az esetben még egyszer meg kell forgatnunk a paraméterek listáját, tehát el kell tüntetnünk még egy paramétert a lenti részben. Majd indul az egész előröl, amíg el nem fogynak a paraméterek.

Az ez utáni case szerkezet még mindig az előfeldolgozás része, mivel itt még nem történik műveletvégzés, hanem csak a paraméterek forgatását végezzük és a kapcsolók értékeit tároljuk. Röviden sorban haladva a példa kapcsolóin:

  • -h vagy --help: Ez egy logikai kapcsoló, nem kell neki érték. Itt forgatunk egyet a paramétereken a shift paranccsal, majd beállítjuk a logikai 1 értéket a megfelelő változóba.
  • -d vagy --debug, -v vagy --version és -e1 vagy --egyeb1: Ez a három kapcsoló is ugyanígy működik. Shift-elés, majd a megfelelő tömb elem 1-re állítása
  • -e2 vagy --egyeb2: Ez egy értéket hordozó kapcsoló, először itt is shift-elünk, ezután ha nincs érték a $value változóban, akkor betöltjük a program elején beállított menekülési értéket, ezáltal a lentebbi műveletek kidolgozásánál érzékelni tudjuk az érték nélkül maradt kapcsolót, hogy ilyenkor hibát dobhassunk.
    Ezután a megfelelő tömb elembe betöltjük a $value értékét – ami vagy a fentebb beállított értéket, vagy a menekülési értéket tartalmazza –, majd még egyszer shift-eljük a paramétereket, ha nem volt egyenlőségjel, tehát fel kellett használnunk a következő paramétert is értékként.
  • -e3 vagy --egyeb3: Ez is logikai kapcsoló, mint az első 4.
  • -e4 vagy --egyeb4: Ez pedig szintén egy értéket hordozó kapcsoló, mint a -e2 vagy --egyeb2, tehát itt is ugyanígy járunk el.
  • *: Ha pedig olyan paramétert kaptunk, ami nem illeszkedik a fentiek egyikére sem, akkor ez az ág teljesül, ahol szintén forgatunk egyet a paramétereken, majd betesszük az iménti ismeretlen argumentumot a nem várt paraméterek tömbjébe.

Ezzel a mechanizmussal egyszerre négy dolgot is elérünk:

  • A kapcsolók sorrendje nem számít, mivel az előfeldolgozó rész végigjárta az összeset, és beállítgatta a megfelelő tömb elemekbe a megfelelő értékeket (logikai 1-eket, vagy egyedi értékeket).
  • Értéket lehet adni egy paraméternek egyenlőségjellel is, és anélkül is (szóközzel elválasztva)
  • A kapcsolókat rövid és hosszú neveikkel is lehet használni, tetszés szerint.
  • Összetettebb kapcsolókat (pl. egymástól függő, vagy egymást kiegészítő kapcsolókat) is használhatunk, mivel már végigjártuk az összeset, ezért egyben ki tudjuk őket értékelni.

Ennek a résznek a legutolsó sorában pedig visszaállítjuk a nem várt paramétereket a program fő paramétereinek helyére. Így szükség esetén később még használhatjuk őket bármire.

 

 

Program futása

Ez után már jöhet a program futási része, ami lényegében az előfeldolgozott változók alapján elvégzi a szükséges műveleteket:

Debug mód

152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# ----------------------------------------------------------
# Program futás
# ----------------------------------------------------------
 
# Debug mód. Ezt vizsgáljuk legelőször, hogy bármikor le tudjon futni
if [[ "${PARAMS[DEBUG]}" == 1 ]]; then
    echo $cyan
    echo "**********************************************"
    echo "DEBUG mód"
    echo "**********************************************"
    echo "Várt paraméterek:"
    for key in "${!PARAMS[@]}"; do echo "$key: ${PARAMS[$key]}" ; done
    echo
    echo "Nem várt paraméterek: ${REMAIN_PARAMETERS[@]}"
    echo "**********************************************"   
    echo $reset
fi

Itt a debug módot láthatjuk, ami egy ciklusban bejárja a PARAMS tömböt és kiírja annak kulcsait és értékeit, amiket az előfeldolgozó részben töltöttünk fel a kapott opciókkal, valamint a nem várt paramétereket is felsorolja – amennyiben a futtatásnál használjuk a -d vagy --debug opciót:

Pélaprogram - Paraméterek rugalmas feldolgozása - Debug mód

Súgó megjelenítése

A következő feltételben a súgót jelenítjük meg:

171
172
173
174
175
176
# Súgó megjelenítése
if [[ "${PARAMS[HELP]}" == 1 ]]; then
    print_help
    echo
    exit 0
fi

Itt a PARAMS tömb HELP logikai értékét figyelve hívjuk a súgó függvényünket, majd ki is lépünk. Ez után már nem futhat le semmilyen más funkció, akármilyen kapcsolókat adtunk is meg (kivéve a fentebb már feldolgozott debug mód):

Pélaprogram - Paraméterek rugalmas feldolgozása - Súgó megjelenítése

Verzió megjelenítése

Ugyanígy ellenőrizzük a verziószámot megjelenítő kapcsolóhoz tartozó változót, és ennek megfelelően meghívjuk a verziószámot megjelenítő függvényünket:

179
180
181
182
183
184
# Verziószám megjelenítése
if [[ "${PARAMS[VERSION]}" == 1 ]]; then
    print_version
    echo
    exit 0
fi

Egyéb műveletek kivitelezései

Logikai kapcsolók

Az "Egyéb 1" logikai kapcsoló kivitelezése a következőképpen történik:

187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# Egyéb 1 logikai kapcsoló kiértékelése és a hozzá tartozó művelet végrehajtása
if [[ "${PARAMS[EGYEB1]}" == 1 ]]; then                     # Itt csak logikai 1 értéket vizsgálunk
    # Saját műveletünk végrehajtása...
    echo "Egyéb 1 logikai kapcsoló érzékelve, művelet végrehajtása..."   # Ez ad valamilyen kimenetet...
    # ...
    # ...
 
    
    # Művelet utáni hiba kiértékelés
    result=$?                                               # Hibakód kiolvasása
    if (( $result == 0 )); then                             # Hiba kiértékelése: Ha nem volt hiba
        echo "${green}Az 'Egyéb 1' művelet sikeresen lefutott.${reset}"
#       exit 0                                              # Itt saját ízlés szerint kilépünk,
                                                            # ha nem szeretnénk további műveleteket végrehajtani ez után.
    else                                                    # Ha hiba lépett fel a műveletünk során, akkor
        echo "${red}Hiba történt az 'Egyéb 1' művelet során!${reset}" >&2
        exit 10                                             # Kilépünk egy magunk által meghatározott hibakóddal
    fi
    echo                                                    # Üres sor kihagyása
fi

Logikai kapcsoló lévén itt csak magának a kapcsolónak a jelenlétét vizsgáljuk, azaz az előfeldolgozó részben betöltött 1-es értéket a megfelelő tömb indexben. Ha ez megvan, akkor lefuttatjuk a kívánt műveletünket, majd kiértékeljük annak eredményességét, és ennek megfelelően adjuk a visszajelzést:

  • A művelet sikeres futása után az első ágban zöld szövegkiemeléssel értesítjük a felhasználót a művelet eredményeiről. Itt még szükség szerint használhatjuk az exit parancsot is, attól függően, hogy szeretnénk-e hogy a művelet után leálljon-e a program futása, vagy inkább engedjük tovább a működését. A példában ezt kikommenteztem, hogy egy futtatással tudjuk figyelni az összes műveletet egyszerre.
  • A kiértékelés hibás ágában pedig piros kiemeléssel értesítjük a felhasználót a hiba kimeneten, majd megszakítjuk a program futását egy egyedi hibakóddal.

Végül a kimenet áttekinthetőségének kedvéért hagyjunk ki egy üres sort. Sajnos ezzel a legtöbb Linux parancsnál nem foglalkoznak, így összeolvad minden. Én ezért igyekszem a lehető legtöbb helyen színeket használni, és szellősebben, tagoltabban, áttekinthetőbben tálalni a kimenetet, hogy egy hosszabb ideig tartó terminálban végzett munka se legyen a szemünknek olyan kimerítő. Egy színtelen, ingerszegény terminálban való ténykedés során figyelmünk is hamarabb gyengül.

Az "Egyéb 3" logikai kapcsoló működési elve ugyanez, így most itt nem részletezem.

Értéket továbbító kapcsolók

Az "Egyéb 2" értéket továbbító kapcsoló kidolgozása pedig az alábbiak szerint történik:

209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# Egyéb 2 kapcsoló kiértékelése és a hozzá tartozó művelet végrehajtása
if [[ "${PARAMS[EGYEB2]}" != "" ]]; then                    # Ha bármilyen érték van benne, lefut ez a rész
 
    # Kötelező érték vizsgálata. Ez a rész opcionális, amennyiben mindenképpen várunk valamilyen értéket ebben a kapcsolóban.
    if [[ "${PARAMS[EGYEB2]}" == "$escape_value" ]]; then   # Ha a változóban a menekülési érték van benne, akkor hibával leállunk.
        echo "${red}Hiba: Az 'Egyéb 2' kapcsoló érzékelve, de nem kapott értéket!${reset}" >&2
        exit 20                                             # Kilépünk egy magunk által meghatározott hibakóddal
    fi
 
    # Saját műveletünk végrehajtása...
    echo "Egyéb 2 kapcsoló érzékelve, művelet végrehajtása..."   # Ez ad valamilyen kimenetet...
    echo "A kapott érték: '${PARAMS[EGYEB2]}'"                # Kiírjuk a kapott értéket. 
    # ...
    # ...
 
    # Művelet utáni hiba kiértékelés
    result=$?                                               # Hibakód kiolvasása
    if (( $result == 0 )); then                             # Hiba kiértékelése: Ha nem volt hiba
        echo "${green}Az 'Egyéb 2' művelet sikeresen lefutott.${reset}"
#       exit 0                                              # Itt saját ízlés szerint kilépünk,
                                                            # ha nem szeretnénk további műveleteket végrehajtani ez után.
    else                                                    # Ha hiba lépett fel a műveletünk során, akkor
        echo "${red}Hiba történt az 'Egyéb 2' művelet során!${reset}" >&2
        exit 21                                             # Kilépünk egy magunk által meghatározott hibakóddal
    fi
    echo                                                    # Üres sor kihagyása
fi

Ennek a kivitelezése annyiban tér el a logikai kapcsolóétól, hogy itt a fő feltételünkben az üres karakterlánctól eltérő eseteket figyeljük. A program elején a PARAM tömbbe amikor beállítottuk az alapértelmezett értékeket, a különböző kapcsolók változói ekkor kapták meg az üres karakterlánc "" értéket. így tehát alapból létezik a tömb megfelelő eleme, de nincs benne semmi. Ezért amikor a program kap egy ilyen kapcsolót, és értéket ad neki, akkor az az érték kerül bele. Ha viszont lemarad az érték egy ilyen kapcsolóról, akkor kerül bele a menekülési érték.

Így tehát először megvizsgáljuk, hogy egyáltalán megkapta-e a program ezt a kapcsolót (ürestől bármi eltérő), majd utána a 213. sorban megvizsgáljuk a benne lévő értéket: ha a menekülési értékünk van benne, akkor az azt jelenti, hogy nem adták meg az értékét ennek a kapcsolónak. ilyenkor hibát generálunk, mivel ettől tér el a logikai kapcsolótól, hogy értéket is kell neki kapnia: piros kiemeléssel a hibakimenetre írunk, majd egy egyedi hibakóddal megszakítjuk a program futását.

És innentől a történet már ugyanaz, mint a logikai kapcsolóknál: saját művelet végrehajtása, hiba kiértékelése és kiírás a kimenetre.

Az "Egyéb 4" értéket továbbító kapcsoló működési elve ugyanez, így most itt ezt sem részletezem.

Működési példák

A következő példákban jól láthatók a program fentebb leírt viselkedése:

Pélaprogram - Paraméterek rugalmas feldolgozása - Kapcsolók működése

Az első futtatásnál láthatjuk a következőket:

  • A kapcsolók/paraméterek mindegy milyen sorrendben érkeznek.
  • A kapcsolók használhatók a rövid és a hosszú neveikkel
  • A kapcsolóknak átadhatunk értéket szóközzel is és egyenlőségjellel is.
  • A megadott érték is tartalmazhat további egyenlőségjelet, nem zavarja meg a működését.

A második futtatásnál pedig az "Egyéb 4" kapcsolónak szándékosan üres értéket "" adtam meg, így láthatjuk a hibakezelését is.

Az alábbi képen pedig láthatjuk, hogy a kapcsolók kis- és nagybetűkkel is működnek:

Pélaprogram - Paraméterek rugalmas feldolgozása - Kapcsolók működése - 2

 

 

Teljes példaprogram

Itt pedig egyben láthatjuk a teljes példaprogramot, ha esetleg ki szeretnénk próbálni, innen könnyebben kimásolhatjuk:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
#!/bin/bash
# ------------------------------------------------------------------------------
# Linuxportál példaprogram: Paraméterek rugalmas feldolgozása
# ------------------------------------------------------------------------------
 
 
# ----------------------------------------------------------
# Kapcsolók/paraméterek alapértelmezett értékei
# ----------------------------------------------------------
declare -A PARAMS
PARAMS=(
    [DEBUG]=""                                              # Debug mód
    [HELP]=""                                               # súgó megjelenítése
    [VERSION]=""                                            # Verzió információk
    [EGYEB1]=""                                             # Egyéb paraméter #1
    [EGYEB2]=""                                             # Egyéb paraméter #2
    [EGYEB3]=""                                             # Egyéb paraméter #3
    [EGYEB4]=""                                             # Egyéb paraméter #4
)
 
 
# ----------------------------------------------------------
# További beállítások
# ----------------------------------------------------------
tabs 4                                                      # Kimenetben megjelenített tab-ok karakter mérete.
shopt -s nocasematch                                        # Kis- és nagybetűk figyelmen kívül hagyása a case szerkezetekben
green=$(tput setaf 2)                                       # Zöld kiemelőszín
red=$(tput setaf 1)                                         # Piros kiemelőszín
cyan=$(tput setaf 6)                                        # Cián kiemelőszín
reset=$(tput sgr0)                                          # Színek alapállapotba állítása
escape_value="***"                                          # "Menekülési érték", ha nincs megadva valamelyik kötelező érték
 
VERSION="1.0"                                               # Program verziója
 
 
# ----------------------------------------------------------
# Függvénytár
# ----------------------------------------------------------
 
# Cím kirakó függvény
function print_title() {
    echo "Linuxportál példaprogram - Paraméterek rugalmas feldolgozása"
}
 
# Verzió kirakó függvény
function print_version() {
    echo "Verzió: $VERSION"
}
 
# Súgó kirakó függvény
function print_help() {
cat << EOF
Használat: 
$(basename $0) [kapcsoló] [érték] ... vagy
$(basename $0) [kapcsoló]=[érték] ...
Kapcsolók:
  -h,       --help          Súgó megjelenítése
  -d,       --debug         Debug mód. Ha meg van adva, akkor kiírja a futás során a használt összes kapcsoló változót.
  -v,       --version       Verzió lekérdezése
  -e1,      --egyeb1        Egyéb 1 funkció futtatása
  -e2=x,    --egyeb2=x      Egyéb 2 tároló paraméter demonstrálása
  -e3,      --egyeb3        Egyéb 3 logikai tároló paraméter demonstrálása
  -e4=x,    --egyeb4=x      Egyéb 4 tároló paraméter demonstrálása
EOF
}
 
# "Kis" súgó kirakó függvény
function print_help2() {
    echo "Súgó megjelenítése: $(basename $0) -h vagy --help"
}
 
 
# ----------------------------------------------------------
# Program indulás
# ----------------------------------------------------------
print_title                                                 # kiírjuk a címet. Ezt minden esetben kitesszük
 
 
# ----------------------------------------------------------
# Paraméterek előfeldolgozása
# ----------------------------------------------------------
 
# Nulla paraméter vizsgálata
if [[ $# -eq 0 ]] ; then                                    # Ha nem kapott paramétert a program, akkor 
    echo "${red}Hiba: Nincsenek paraméterek!${reset}" >&2  # Hibakimenetre írunk.
    print_help2                                             # Kis súgó kirakása
    exit 1                                                  # és kilépés 1-es hibakóddal
fi
 
REMAIN_PARAMETERS=()                                        # Maradék (nem várt) paraméterek tömbjének létrehozása
while [[ $# -gt 0 ]] ; do                                   # Ciklus indul, és megy, amíg maradt paraméter.
    parameter="$1"                                          # Soron következő első paraméter kiolvasása
    need_shift=0                                            # "shift szükséges" flag nullázása.
 
    # Egyenlőség jeles paraméterek kezelése
    if [[ $parameter = *"="* ]]; then                       # Ha a soron következő első paraméter tartalmaz "=" jelet, akkor
        key=${parameter%%=*}                                # a kulcs lesz az első egyenlőség jel előtti rész,
        value=${parameter#*=}                               # az érték pedig az első egyenlőség jel utáni rész.
    else                                                    # Ha a paraméterben nem volt egyenlőség jel, akkor
        key=$parameter                                      # a kulcs lesz maga a kapott paraméter
        value=$2                                            # és az utána lévő paraméter pedig lesz az esetleges érték.
        need_shift=1                                        # Itt jelezzük a lenti részeknek, hogy kell-e a plusz paraméter forgatás.
    fi
 
    # Kapcsolók elágazása
    case $key in                                            # case szerkezet: megvizsgáljuk a kulcsot, hogy mit tartalmaz.
        -h|--help)                                          # Súgó (logikai) kapcsoló érkezett...
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            PARAMS[HELP]=1                                  # Logikai érték beállítása az ennek fentartott tömb elemében.
        ;;
        -d|--debug)                                         # debug (logikai) kapcsoló ékezett...
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            PARAMS[DEBUG]=1                                 # Logikai érték beállítása az ennek fentartott tömb elemében.
        ;;
        -v|--version)                                       # verzió (logikai) kapcsoló érkezett...
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            PARAMS[VERSION]=1                               # Logikai érték beállítása az ennek fentartott tömb elemében.
        ;;
        -e1|--egyeb1)                                       # egyeb1 (logikai) kapcsoló érkezett...
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            PARAMS[EGYEB1]=1                                # Logikai érték beállítása az ennek fentartott tömb elemében.
        ;;
        -e2|--egyeb2)                                       # egyeb1 (értéket tároló) kapcsoló érkezett...
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            [[ "$value" == "" ]] && value=$escape_value     # Ha nem kapott értéket ez a kapcsoló, akkor beletesszük a menekülési értéket
            PARAMS[EGYEB2]=$value                           # a fentebb megállapított érték tárolása az ennek fentartott tömb elemében.
            [[ "$need_shift" == 1 ]] && shift               # Ha szükséges a paraméterek forgatása, akkor még egyszer forgatjuk.
        ;;
        -e3|--egyeb3)                                       # egyeb3 (logikai) kapcsoló érkezett...
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            PARAMS[EGYEB3]=1                                # Logikai érték beállítása az ennek fentartott tömb elemében.
        ;;
        -e4|--egyeb4)                                       # egyeb4 (értéket tároló) kapcsoló érkezett...
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            [[ "$value" == "" ]] && value=$escape_value     # Ha nem kapott értéket ez a kapcsoló, akkor beletesszük a menekülési értéket
            PARAMS[EGYEB4]=$value                           # a fentebb megállapított érték tárolása az ennek fentartott tömb elemében.
            [[ "$need_shift" == 1 ]] && shift               # Ha szükséges a paraméterek forgatása, akkor még egyszer forgatjuk.
        ;;
        *)                                                  # Ha ismeretlen paraméter jön a sorban, akkor
            shift                                           # Shift-eljük (forgatjuk) a paramétereket
            REMAIN_PARAMETERS+=("$key")                     # Felvesszük a kapott paramétert a maradék (nem várt) paraméterek tömbjébe.
        ;;
    esac                                                    # case vége
done                                                        # ciklus vége
# Idáig tehát "elfogyasztottuk" az összes paramétert a shift-ek által.
 
# Beállítjuk a megmaradt egyéb paramétereket a szabványos $1, $2, $3, stb változókba
# Így könnyebb a maradékot feldolgozni később, amennyiben szükséges.
set -- "${REMAIN_PARAMETERS[@]}"
 
 
# ----------------------------------------------------------
# Program futás
# ----------------------------------------------------------
 
# Debug mód. Ezt vizsgáljuk legelőször, hogy bármikor le tudjon futni
if [[ "${PARAMS[DEBUG]}" == 1 ]]; then
    echo $cyan
    echo "**********************************************"
    echo "DEBUG mód"
    echo "**********************************************"
    echo "Várt paraméterek:"
    for key in "${!PARAMS[@]}"; do echo "$key: ${PARAMS[$key]}" ; done
    echo
    echo "Nem várt paraméterek: ${REMAIN_PARAMETERS[@]}"
    echo "**********************************************"   
    echo $reset
    echo                                                    # Üres sor kihagyása
fi
 
# Súgó megjelenítése
if [[ "${PARAMS[HELP]}" == 1 ]]; then
    print_help
    echo
    exit 0
fi
 
 
# Verziószám megjelenítése
if [[ "${PARAMS[VERSION]}" == 1 ]]; then
    print_version
    echo
    exit 0
fi
 
 
# Egyéb 1 logikai kapcsoló kiértékelése és a hozzá tartozó művelet végrehajtása
if [[ "${PARAMS[EGYEB1]}" == 1 ]]; then                     # Itt csak logikai 1 értéket vizsgálunk
    # Saját műveletünk végrehajtása...
    echo "Egyéb 1 logikai kapcsoló érzékelve, művelet végrehajtása..."   # Ez ad valamilyen kimenetet...
    # ...
    # ...
 
    
    # Művelet utáni hiba kiértékelés
    result=$?                                               # Hibakód kiolvasása
    if (( $result == 0 )); then                             # Hiba kiértékelése: Ha nem volt hiba
        echo "${green}Az 'Egyéb 1' művelet sikeresen lefutott.${reset}"
#       exit 0                                              # Itt saját ízlés szerint kilépünk,
                                                            # ha nem szeretnénk további műveleteket végrehajtani ez után.
    else                                                    # Ha hiba lépett fel a műveletünk során, akkor
        echo "${red}Hiba történt az 'Egyéb 1' művelet során!${reset}" >&2
        exit 10                                             # Kilépünk egy magunk által meghatározott hibakóddal
    fi
    echo                                                    # Üres sor kihagyása
fi
 
 
# Egyéb 2 kapcsoló kiértékelése és a hozzá tartozó művelet végrehajtása
if [[ "${PARAMS[EGYEB2]}" != "" ]]; then                    # Ha bármilyen érték van benne, lefut ez a rész
 
    # Kötelező érték vizsgálata. Ez a rész opcionális, amennyiben mindenképpen várunk valamilyen értéket ebben a kapcsolóban.
    if [[ "${PARAMS[EGYEB2]}" == "$escape_value" ]]; then   # Ha a változóban a menekülési érték van benne, akkor hibával leállunk.
        echo "${red}Hiba: Az 'Egyéb 2' kapcsoló érzékelve, de nem kapott értéket!${reset}" >&2
        exit 20                                             # Kilépünk egy magunk által meghatározott hibakóddal
    fi
 
    # Saját műveletünk végrehajtása...
    echo "Egyéb 2 kapcsoló érzékelve, művelet végrehajtása..."   # Ez ad valamilyen kimenetet...
    echo "A kapott érték: '${PARAMS[EGYEB2]}'"                # Kiírjuk a kapott értéket. 
    # ...
    # ...
 
    # Művelet utáni hiba kiértékelés
    result=$?                                               # Hibakód kiolvasása
    if (( $result == 0 )); then                             # Hiba kiértékelése: Ha nem volt hiba
        echo "${green}Az 'Egyéb 2' művelet sikeresen lefutott.${reset}"
#       exit 0                                              # Itt saját ízlés szerint kilépünk,
                                                            # ha nem szeretnénk további műveleteket végrehajtani ez után.
    else                                                    # Ha hiba lépett fel a műveletünk során, akkor
        echo "${red}Hiba történt az 'Egyéb 2' művelet során!${reset}" >&2
        exit 21                                             # Kilépünk egy magunk által meghatározott hibakóddal
    fi
    echo                                                    # Üres sor kihagyása
fi
 
 
# Egyéb 3 logikai kapcsoló kiértékelése és a hozzá tartozó művelet végrehajtása
if [[ "${PARAMS[EGYEB3]}" == 1 ]]; then                     # Itt csak logikai 1 értéket vizsgálunk
    # Saját műveletünk végrehajtása...
    echo "Egyéb 3 logikai kapcsoló érzékelve, művelet végrehajtása..."   # Ez ad valamilyen kimenetet...
    # ...
    # ...
 
    # Művelet utáni hiba kiértékelés
    result=$?                                               # Hibakód kiolvasása
    if (( $result == 0 )); then                             # Hiba kiértékelése: Ha nem volt hiba
        echo "${green}Az 'Egyéb 3' művelet sikeresen lefutott.${reset}"
#       exit 0                                              # Itt saját ízlés szerint kilépünk,
                                                            # ha nem szeretnénk további műveleteket végrehajtani ez után.
    else                                                    # Ha hiba lépett fel a műveletünk során, akkor
        echo "${red}Hiba történt az 'Egyéb 3' művelet során!${reset}" >&2
        exit 30                                             # Kilépünk egy magunk által meghatározott hibakóddal
    fi
    echo                                                    # Üres sor kihagyása
fi
 
 
# Egyéb 4 kapcsoló kiértékelése és a hozzá tartozó művelet végrehajtása
if [[ "${PARAMS[EGYEB4]}" != "" ]]; then                    # Ha bármilyen érték van benne, lefut ez a rész
 
    # Kötelező érték vizsgálata. Ez a rész opcionális, amennyiben mindenképpen várunk valamilyen értéket ebben a kapcsolóban.
    if [[ "${PARAMS[EGYEB4]}" == "$escape_value" ]]; then   # Ha a változóban a menekülési érték van benne, akkor hibával leállunk.
        echo "${red}Hiba: Az 'Egyéb 4' kapcsoló érzékelve, de nem kapott értéket!${reset}" >&2
        exit 40                                             # Kilépünk egy magunk által meghatározott hibakóddal
    fi
 
    # Saját műveletünk végrehajtása...
    echo "Egyéb 4 kapcsoló érzékelve, művelet végrehajtása..."   # Ez ad valamilyen kimenetet...
    echo "A kapott érték: '${PARAMS[EGYEB4]}'"                # Kiírjuk a kapott értéket. 
    # ...
    # ...
 
    # Művelet utáni hiba kiértékelés
    result=$?                                               # Hibakód kiolvasása
    if (( $result == 0 )); then                             # Hiba kiértékelése: Ha nem volt hiba
        echo "${green}Az 'Egyéb 4' művelet sikeresen lefutott.${reset}"
#       exit 0                                              # Itt saját ízlés szerint kilépünk,
                                                            # ha nem szeretnénk további műveleteket végrehajtani ez után.
    else                                                    # Ha hiba lépett fel a műveletünk során, akkor
        echo "${red}Hiba történt az 'Egyéb 4' művelet során!${reset}" >&2
        exit 41                                             # Kilépünk egy magunk által meghatározott hibakóddal
    fi
    echo                                                    # Üres sor kihagyása
fi

 

 

Konklúzió

Íme tehát egy shell szkript, ami rugalmasan tudja kezelni a kapott paramétereket, valamint komplett hibakezelést is tartalmaz. Persze ez még fűszerezhető sok mindennel, de ez már a felhasználási területen is múlik, hogy milyen célra szeretnénk elkészíteni programunkat. Célszerű eltenni sablonnak, és az újonnan készülő programjainkat csak ebbe építeni, és már készen is van a paraméterek kezelése is.

 

Kapcsolódó tartalom, hasznos linkek: