BashExamples

1. óra

Háttérismeretek

Számítógépes Architektúrák gyakorlatokon shell scriptek írásával fogunk foglalkozni Unix alapú operációs rendszerekben. Mit tekintünk Unix alapú rendszernek, és mik is azok a shell scriptek?

Az eredeti AT&T Unix operációs rendszert még a hatvanas években fejlesztették, és sok későbbi operációs rendszernek szolgált alapjául. Ezeknek egy része tényleges Unix operációs rendszer (Solaris), mások pedig Unixhoz hasonló rendszerek: Mac OS X és a számtalan Linux disztribúció.

Az informatikus tanulmányok során érdemes megismerkedni legalább egy Linux operációs rendszerrel közelebbről (tehát érdemes saját otthoni gépre is feltelepíteni, akár virtuális gépen). A tanszéki gépeken ennek a jegyzetnek az írásakor Linux Mint van telepítve (a Mint egy Linux disztribúció). Ezen felül a következő Linux disztribúciókat érdemes megemlíteni:

A shell kezelésének szempontjából nem lényeges, hogy melyik Linux operációs rendszert használjuk. De mi az a shell?

Az operációs rendszerek központi eleme a kernel (tehát mag). A kernel felelős az alapvető funkciók ellátásáért (például processzek kezelése, memória menedzselés, fájlrendszeri művelet; ezekről bővebben Operációs Rendszerek tárgyból). A shell (héj) egy parancsértelmező, ami a felhasználó szöveges utasításait fogja a kernel számára is értelmezhető utasításokká alakítani.

Tehát a shellt mi úgy fogjuk látni, mint egy terminálban futó parancsértelmezőt. Az alább látható képen egy terminál látható, amiben egy shell fut.

terminal screenshot

A fönt látható terminálban a Bash parancsértelmező fut, a parancsok pedig létrehoznak két mappát; SzArGyak (Számítógépes Architektúrák Gyakorlat) és benne a lesson1 mappát, majd belépnek a lesson1 mappába. Egy terminálban futhat másféle shell is, nem csak Bash (más shellek például: sh, zsh). A Windows operációs rendszereknek is van shellje, csak ott Powershellként és Command Line-ként találkozhatunk velük. Ezeknek más a szintaktikája, mint a Bash-nek. Mi a gyakorlatok keretében Bash scripteket fogunk írni.

Bash: az elnevezés egy szójátékból adódik. A mostani shell szabvány az eredeti AT&T Unixhoz tartozó shell szabványból indul ki, amelyet Stephen Bourne dolgozott ki. Bash = Bourne Again Shell.

A Linux alapú rendszereknek az egyik nagy erőssége, hogy rengeteg előre elkészített “paranccsal” rendelkeznek (kb. 700-1000). A parancsokat más néven “eszközöknek” (tool) nevezik, elvégre ezek többsége külső segédprogram. Néhány példát említve, hogy mire alkalmasak a parancsok:

A későbbiekben feladatokon keresztül fogunk megismerkedni az ilyen parancsokkal, és ezeknek a használatával. A parancsok egymással kombinálhatók, és általuk komplex feladatok hajthatók végre.

Legelőször nyissunk egy terminált (Ctrl + Alt + T), amiben bash fut (feltehetően ez a default). A további feladatokat terminálban fogjuk csinálni.

Példák

1. példa

Hozzunk létre egy mappát, amit a gyakorlatok feladataihoz fogunk használni. A neve legyen szgyak. Ebben a mappában hozzunk létre egy másikat, aminek neve legyen lesson1. Lépjünk bele ebbe a mappába!

A következő parancsokkal például ez megvalósítható:

mkdir szgyak
mkdir szgyak/lesson1
cd szgyak/lesson1

A fönti példában a következőket figyeljük meg:

Ezzel kapcsolatban az is megfigyelhető, hogy a mappaneveket / (slash) jel választja el egymástól. Ez Unix alapú rendszereknél így van, míg Windowsban ez a jel \ (backslash).

2. példa

Írjuk ki, hogy hol tartózkodunk épp a mapparendszerben, az abszolút ösvénnyel!

Erre használhatjuk a pwd parancsot is (print working directory). Ez kiírja az aktuális mappát (working directory).

Üssük be a parancsot!

pwd
# valami ilyesmit kell kapnunk: /home/bbalage/szgyak/lesson1
# A hashmark (#) kommentet jelent bashben
# Tehát amit # után írsz, azt a parancssor nem fogja értelmezni

A kapott output eleje nem egyezik azzal, amit a terminálban olvashatunk. Ha megfigyeljük, akkor a ~ jel felcserélődött egy másik “ösvénnyel” (vagy path-szal). A ~ jel egy rövidítés a saját felhasználói gyökér mappánkra. Nálam a ~ jelentése /home/bbalage, míg más felhasználóknál ez más lesz.

Szintén megemlítendő, hogy Unix rendszerben a tényleges gyökérmappát a / jel azonosítja. Ha a gyökérből kiindulva adjuk meg a path-t, akkor a “full path-t” adjuk meg. Ellenben, ha a jelenlegi jegyzékből (working directory) indulunk ki, akkor a “relative path-t” (relatív ösvény).

3. példa

Lépjünk be a gyökér mappába, és írassuk ki a tartalmát!

Egy mappa tartalmának kiírására az ls parancsot tudjuk használni. Az ls parancsnak opcionálisan megadhatjuk, hogy melyik jegyzék tartalmát írja ki. Ha nem adunk meg semmit, akkor a working directory tartalmát írja ki.

cd /
ls
# Ha nem akarunk belépni a mappába, csak ki akarjuk írni
# a tartalmát, akkor # ezt az alábbi paranccsal egy lépésben
# is megtehetjük:
ls /

A kép szemlélteti, hogy mit fogunk kapni:

terminal screenshot

Amiket látunk, azok rendszermappák. Csak néhányat kiemelve ezek közül:

4. példa

Lépjünk be a szgyak/lesson1 mappába, és hozzunk létre egy whatever.txt nevű fájlt!

cd ~/szgyak/lesson1
touch whatever.txt

A cd ~/szgyak parancsot mindegy, hogy hol adjuk ki, ugyanis a ~ jel a jelenleg belépett felhasználó home mappájának rövid jelölése, ezért a ~/szgyak egy full path, nem pedig relative.

A touch parancs frissíti egy argumentumként megadott fájlt utolsó elérési és utolsó változtatási dátumát (last accessed, last modified). Ha nem létezik a fájlt, amit megadtunk, akkor létrehozza azt. Így a touch parancsot szoktuk fájlok létrehozására használni.

5. példa

Írassuk ki a szgyak/lesson1 mappa teljes tartalmát a benne lévő fájlok tulajdonosaival együtt!

cd ~/szgyak/lesson1
ls -la
ls -la ~/szgyak/lesson1 # ha így adjuk ki a parancsot,
                        # akkor mindegy mi épp a working
                        # directory

Azt látjuk, hogy az ls parancsot némileg máshogy adtuk ki, mint eddig. Egy parancs működését lehet módosítani kapcsolók segítségével. Egy kapcsoló a parancs után jön, de az elhelyezkedése változhat parancsonként és kapcsolónként. Szinte minden parancsnak vannak kapcsolói, némelyiknek egészen rengeteg, és egészen bonyolultak. Ahhoz, hogy megnézzük, mik egy parancs kapcsolói, és mire valók, hogyan működnek, vagy az internetet, vagy a manual page-et hívjuk segítségül.

A következő paranccsal megnézzük az ls parancs manual entry-ét:

man ls

A manual page-en lehet lefelé görgetni a nyilakkal, és ki lehet lépni a q karakter lenyomásával. Szinte minden parancshoz lesz manual entry, és ezeket használjuk is!

Próbáljuk ki az ls parancs kapcsolóit!

ls -l # kötőjellel adjuk meg a kapcsolókat
ls -a # ez egy másik kapcsoló
ls -la # ez a két előző kapcsoló kombinálva
       # (mintha mindkettőt kiadtuk volna)

A manualból megtudhatjuk ezekről a következőket:

A ponttal kezdődő nevek valamilyen kisegítő dologhoz szoktak tartozni, amit nem szeretnénk, hogy az a felhasználó is lásson, aki csak kattintgat egy file explorer felületen. Ezek lehetnek parancssori fájlok, konfigurációk, de gyakorlatilag bármi más is, aminek úgy döntöttünk, hogy ponttal kezdődő nevet adunk.

6. példa

Hozzunk létre egy tmp mappát a szgyak/lesson1 jegyzéken belül, és írassuk ki a tartalmát! Mi a mappa tartalma?

mkdir ~/szgyak/lesson1/tmp
# ha a working directory a ~/szgyak/lesson1 akkor elég ennyi is:
# mkdir tmp
ls -a ~/szgyak/lesson1/tmp # full path
ls -a tmp # relative path; függ a working directory-tól
          # (tehát attól, hogy "hol vagyunk")

Azt látjuk. hogy még az üres jegyzéknek is van két bejegyzése: . és .. Az egy pontból álló bejegyzés magára a jegyzékre mutat, míg a két pontból álló bejegyzés a szülő jegyzékre (amiben az aktuális jegyzék van). Tehát a következő paranccsal visszalépünk a szgyak jegyzékbe:

cd ..

Az ilyen visszalépő directory neveket lehet láncolni is. Az alábbival kettőt lépünk “vissza”:

cd ../..

És így tovább. Az alábbival nem csináltunk semmit, ugyanis . arra a mappára utal, amiben található.

cd .

7. példa

Töröljük a tmp directory-t!

cd ~/szgyak/lesson1
rmdir tmp
# ha kihagyjuk a cd-t, akkor természetesen megtehetjük ezt is:
# rmdir ~/szgyak/lesson1/tmp
# ugyanaz történik, csak egyik esetben full path, másik esetben
# relative path a hivatkozás típusa

rmdir = remove directory; kellően beszédes név, hogy ne kelljen magyarázni.

8. példa

Töröljük a whatever.txt fájlt!

rm ~/szgyak/lesson1/whatever.txt

rm = remove; szintén beszédes. Annyit viszont érdemes megemlíteni, hogy ez a parancs nem a kukába rakja a fájlokat, hanem ténylegesen törli őket. Nem fogjuk tudni a kukából visszaszerezni azt, amitől így szabadultunk meg.

9. példa

Hozzuk létre a következő mappaszerkezetet a ~/szgyak/lesson1 mappán belül (minden fájlt hagyjunk üresen):

|
|-src (mappa)
 |- main.c (fájl)
 |- util.h (fájl)
 |- util.c (fájl)
|-assets (mappa)
 |-textures (mappa)
  |-xy.png (fájl)
  |-wz.png (fájl)
  |-readme.txt (fájl)
 |-maps (mappa)
|-build (mappa)
 |-release (mappa)
 |-debug (mappa)

A jegyzékeket könnyen létrehozhatjuk az alábbi módon:

cd ~/szgyak/lesson1
mkdir src
mkdir assets
mkdir assets/textures
mkdir assets/maps
mkdir build
mkdir build/release
mkdir build/debug

Viszont ezzel nem tanultunk semmi újat, és kettővel több parancsot adtunk ki, mint szükséges!

Ha elolvassuk a mkdir parancs manual entry-ét (man mkdir), akkor megtudjuk, hogy van neki egy -p kapcsolója, ami a szülő mappa létrehozására is utasít. Például az alábbi parancs alapból hibával elbukik:

mkdir build/release

Ez azért van, mert nincs build mappa, amiben létre lehet hozni a release mappát. A -p kapcsoló annyiban módosítja a működést, hogy az ehhez hasonló esetekben a szülő mappát is létrehozza a parancs (-p mint parent). Így kiadva a parancsokat:

cd ~/szgyak/lesson1
mkdir src
mkdir -p assets/textures
mkdir assets/maps
mkdir -p build/release
mkdir build/debug

A fájlokat egyszerűen hozzuk létre a touch paranccsal!

touch src/main.c
touch src/util.c
touch src/util.h
touch assets/textures/xy.png
touch assets/textures/wz.png
touch assets/textures/readme.txt

10. példa

Írassuk ki a mappák tartalmát, karaktergrafikusan rendezett formában!

tree
# igen, ennyi a parancs. Adjuk ki a lesson1 mappában!

A tree parancs nem minden rendszerre van telepítve, viszont pont azt csinálja, amire szükségünk van. Ellenőrizzük, hogy a kapott mappaszerkezet olyan-e, mint az alább látható screenshoton. Ha nem olyan, akkor valamit elrontottunk.

terminal screenshot

11. példa

Töröljük az összes png fájlt a textures mappában!

rm assets/textures/*.png

A csillag minden szövegre illeszkedni fog (a reguláris kifejezésekről később majd bővebben). Ellenőrizzük a tree paranccsal, hogy a png fájlok (és csak azok) eltűntek-e. Erre az ellenőrzésre használhatjuk az ls assets/textures parancsot is (amelyik szimpatikus).

12. példa

Hozzunk létre egy utils mappát az src mappán belül, és helyezzük át bele az util.c és az util.h fájlokat!

mkdir src/utils
mv src/util.h src/utils/util.h
mv src/util.c src/utils/util.c

mv = move. A működése meglehetősen egyszerű. Első argumentuma annak a fájlnak vagy mappának az elérési útja (path), amit át akarunk helyezni, második argumentuma pedig az új helyen érvényes elérési út.

Tehát az a fájl, amit eddig src/util.h ösvényen értünk el, azt a parancs kiadása után src/utils/util.h elérési úton fogjuk elérni.

13. példa

Nevezzük át a textures mappában található readme.txt fájlt readme_sprites.txt fájlra.

mv assets/textures/readme.txt assets/textures/readme_sprites.txt

Átnevezésre az mv parancsot szoktuk használni a föntiek szerint.

14. példa

Másoljuk át a readme_sprites.txt fájlt a maps mappába readme_maps.txt néven!

cp assets/textures/readme_sprites.txt assets/maps/readme_maps.txt

cp = copy. A működése ugyanolyan, mint az mv parancsé, csak itt másolunk, nem áthelyezünk.

15. példa

Töröljük a maps és a build mappát!

rm -r assets/maps
rm -r build

Az -r kapcsoló jelentése recursive, hatása pedig az, hogy amennyiben az rm parancsnak egy mappát adunk meg, akkor rekurzívan törli a mappa teljes tartalmát (és minden a mappában lévő mappa tartalmát), mielőtt törli magát a mappát.

Összefoglalás

A következő parancsokat tanultuk:

Feladatok

Önálló gyakorló feladatok.

1. feladat

Induljunk felderítő útra a fájlrendszerben! Nézzünk bele más felhasználók home mappáiba! Keressük meg a legvalószínűbb helyet, ahova az értékes beadandóikat pakolják majd! Ha a fájljaik mindenki számára olvashatóak, akkor meg is nézhetjük őket. Ha írhatók, akkor törölhetjük is őket. Tanulság: ügyeljünk a jogosultságokra (következő órán megnézzük őket).

2. feladat

Módosítsuk az util.h utolsó változtatási dátumát, de csak a változtatási dátumát! Figyeljünk arra, hogy a parancs alapból mind az utolsó elérési, mind az utolsó módosítási dátumot módosítja! A feladat megoldásához olvassuk ki a megfelelő kapcsolót a touch parancs manual entry-éből (önálló utánanézés).

3. feladat

Hozzunk létre egy include mappát a lesson1 mappán belül. Másoljuk bele az util.h fájlt, de adjuk ki úgy a parancsot, hogy csak akkor történjen másolás, ha a célmappában lévő azonos nevű fájl nem létezik, vagy elavult! (csak akkor másolj, ha szükséges) Keressük ki a megfelelő kapcsolót a cp parancs manual entry-éből!