PočetnaHelpdeskŠirenja i regularni izrazi u UNIX-u

Širenja i regularni izrazi u UNIX-u


Ukoliko ste pažljivo pročitali prvi dio naše škole UNIX-a, sada već znate na osnovnoj razini manipulirati mapama, preko Terminala putovati kroz njih, stvarati nove mape te brisati postojeće. Imali ste priliku naučiti i osnove upisa rezultata naredbe u datoteku, kao i ispisa datoteke te spajanje izlaza jedne naredbe na ulaz druge. U ovom nastavku dajemo vam mogućnost proširiti znanje u vidu učenja rada sa širenjima u naredbenom retku, kao i regularnim izrazima što će vam omogućiti brži i efikasniji rad u UNIX okruženju. Međutim, prvo ćemo napraviti kratak uvod u varijable. Dogovorno, u tekstu ćemo linije koje se upisuju u terminal omeđivati uglatim zagradama, no linije se u Terminal upisuju bez njih.
Kao u svakom programskom jeziku, tako i u bash ljusci imamo varijable,koje su u ovom slučaju tipa string (tekst). Kasnije ćemo vidjeti da rad s njima nije intuitivan i jednostavan kao kod uobičajenih programskih jezika.

Zasada, dovoljno je reći da se varijabli dodjeljuje vrijednost na uobičajeni način; recimo želimo varijablu imena “var” inicijalizirati vrijednošću “Hello World!”. To ćemo učiniti upisom linije [var=’Hello World!’]. Bitno je znati da je UNIX ljuska izuzetno osjetljiva na razmake tako da, ukoliko znak jednakosti odvojimo razmacima, ovo pridjeljivanje neće funkcionirati (ljuska će redak shvatiti kao pozivanje naredbe imena “var”).

Za ispis varijabli (ili cijelih izraza) koristi se naredba echo. Ona jednostavno argument, nakon svih širenja (više o širenjima kasnije), ispisuje na standardni izlaz, odnosno ekran. Možemo joj predati, kao bilo kojoj drugoj naredbi, više argumenata odvojenih razmacima koji će svi biti ispisani.

Kada se varijabla ne inicijalizira, nego se čita njena vrijednost (u naredbi ili u izrazu), predznačuje se znakom “$”. Tako ćemo našu varijablu var ispisati pomoću [echo $var]. Primijetite da, ukoliko ne predznačimo ime varijable, naredba shvaća ime varijable kao string te će ga ispisati umjesto vrijednosti.

Postoje određene, rezervirane varijable shella (ljuske), a neke od njih su: $PWD (sadrži putanju radnog direktorija), $PATH (sadrži “defaultne” putanje do direktorija koji sadrže programe koji se izvršavaju u naredbenom retku), $HOME (sadrži putanju matičnog direktorija) i $USER (sadrži ime aktivnog korisnika). Sadržaj ovih varijabli možete vidjeti pomoću naredbe echo, kao što smo to učinili s varijablom var. Možete i mijenjati sadržaj ovih varijabli, iako se to ne preporuča, jer time možete izravno utjecati na rad ljuske, a time možda postići i neočekivano ponašanje iste.

Iako vam pojam širenja može zvučati apstraktno, to je mehanizam koji je prisutan u svakom programskom jeziku pa tako i u bash-u. Radi se o tome da shell pojedine upisane dijelove kôda prvo “širi”, a zatim interpretira, odnosno izvršava. Najjednostavniji, opći primjer ovoga je recimo znak *, kojeg ste imali priliku koristiti pri pretraživanju u nekakvom tekst editoru ili u traženju datoteka i slično. On bi u tom (općem) slučaju predstavljao bilo koji znak ili niz znakova koji je moguć u kontekstu u kojem se koristi. U pozadini izvršavanja bi se on “proširio” te bi nakon toga predstavljao nešto novo (skup svih mogućih stringova) i naposlijetku bi se pretraživanje počelo vršiti. Možete čak isprobati ovaj znak upisom [ls *], čime ćete dobiti ispis sadržaja svih mapa unutar aktivne mape.

Prvo od širenja koje ćemo spomenuti (i koje se prvo izvršava) je širenje vitičastih zagrada. Ono je vrlo praktično za upotrebu, jer omogućuje “masovne” radnje, kao npr. stvaranje većeg broja mapa čiji su nazivi numerirani. Širenje se koristi tako da, na mjesto gdje želimo generirati stringove iste uokvirimo u vitičaste zagrade te odvojimo zarezima. Isprobajte liniju [mkdir Folder{‘ prvi’,’ drugi’,’ treci’}]. Nakon izvršavanja provjerite rezultat naredbom ls – trebali biste dobiti tri nove mape koje se zovu Folder prvi, Folder drugi i Folder treci. Na isti način ih možete obrisati – kako ne biste ponovno morali upisivati sve, strelicom gore se vratite na liniju kôda s naredbom mkdir te umjesto nje upišite rmdir.

Dakle, ovo je bio jedan način širenja vitičastih zagrada, a drugi je navođenje početka i kraja niza (odvojenih nizom “..”) koji želite generirati; isprobajte naredbom [mkdir f{9..11} {a..c}]. U ovom primjeru odmah možete primijetiti i to da se ovih širenja (kao i bilo kojih drugih) može upotrijebiti proizvoljan broj unutar jednog stringa. Također primijetite da smo razmak morali escapeati pomoću znaka “”. Nakon izvršavanja naredbe trebali biste dobiti devet novih mapa s nazivima “f9 a”, “f9 b”,…, “f11 c”.

Neka od širenja ćemo preskočiti, jer nisu nužna za osnove rada u bash ljusci.
Sljedeće širenje je širenje znakom “$”. Ulogu širenja varijable smo već vidjeli na prvom primjeru s varijablom imena “var”; taj znak je u tom slučaju proširio ime varijable u njenu vrijednost. Ime varijable je moguće staviti u vitičaste zagrade kako bi se izbjegla nejednoznačnost; npr. [echo $var5] će ispisati vrijednost varijable var5, koja ne postoji ukoliko ju niste stvorili, dok će [echo ${var}5] ispisati vrijednost varijable var (koju ste stvorili prateći članak) spojenu s brojem 5.

Nadalje, znakom $ možemo obavljati i zamjenu naredbe – napisana naredba unutar zagrada i sve skupa predznačeno znakom $ vraća string koji je izlaz (rezultat) izvršavanja te naredbe. Ovaj efekt možete isprobati unošenjem [echo $(ls)], gdje naredba ls vraća popis datoteka, on se pomoću širenja pretvara u string te naposlijetku ispisuje na ekran naredbom echo. Primjer je trivijalan, no primijetite da možete na ovaj način recimo nizati izlaze više naredbi te na kraju ih sve zajedno ispisati naredbom echo (ili dobiveni string predati nekoj drugoj naredbi i slično). Ovakva širenja možete i gnijezditi jedno unutar drugog.

Ako želite vršiti matematičke operacije, morate koristiti aritmetičko širenje pomoću $((aritmetički_izraz)). Naprimjer, inicijalizirajte varijablu a pomoću [a=2] pa nakon toga upišite [echo $(($a+3))]. Trebali biste dobiti rezultat 5, koji je zbroj vrijednosti varijable a i broja 3. Izrazi se pišu klasično, kao i u svakom drugom programskom jeziku – možete se koristiti uobičajenim operatorima za zbrajanje, oduzimanje, množenje, dijeljenje, ostatak pri dijeljenju (modulo, operator %), potenciranje (operator **) kao i zagradama za grupiranje podizraza.
Nakon aritmetičkog širenja, slijedi širenje imena datoteka. Spomenuli smo već jedan znak, koji propada ovom širenju, a to je znak “*” koji predstavlja sve moguće znakovne nizove (stringove) koji su logični unutar konteksta. Recimo, ukoliko pokušate upisati [mkdir *], nećete stvoriti beskonačno puno novih mapa, nego ćete dobiti grešku da navedene mape već postoje. Dakle, u ovom slučaju, znak * se podudarao sa nazivima svih mapa u aktivnom direktoriju, jer je to njegov trenutni logični kontekst. Ako pokušate s [rmdir *], jednostavno ćete odjednom obrisati sve direktorije unutar aktivnog.

Pomoću “?” se predstavlja točno jedan, bilo koji znak. Naprimjer, mape stvorene pomoću [mkdir folder{1..5}] možete obrisati pomoću [rmdir folder?] jer će ova linija kôda obrisati sve mape koje počinju s “folder”, nakon čega slijedi točno jedan i to bilo koji znak. I naposlijetku, imamo uglate zagrade pomoću kojih se također podudara jedan znak, a koji pripada skupu znakova navedenom u zagradama. Znakovi se mogu nabrajati jedan za drugim, bez ikakvih separatora, može se definirati sekvenca pomoći znaka “-” ili se pak može, pomoću znaka ^, definirati podudaranje sa svim znakovima osim onih koji su navedeni unutar zagrada. Tako će [rmdir [abc]*], kao i [rmdir [a-c]*] obrisati sve mape čija imena počinju s a, b ili c, nakon čega slijedi proizvoljan niz znakova (koji može biti i prazan niz, dakle, briše i mapu imena “b”). Nadalje, [rmdir [^abc]*] će obrisati sve mape koje ne počinju s a, b ili c. Ovi svi rezervirani znakovi se mogu koristiti i u svim kombinacijama, npr. [rmdir ?[a-z]*[^012]] obrisat će sve mape koje na početku imena imaju točno jedan, bilo koji znak, zatim malo slovo abecede, a zadnji znak u imenu nije broj 0, 1 ili 2.

Regularni izraz je pojam koji je općenitiji od samog programiranja u bashu.
Predstavlja niz znakova, koji prema određenim sintaksnim pravilima opisuje nekakav tekst (string). Moglo bi se pojednostavljeno reći da regularni izraz predstavlja nekakvu “formulu” koja opisuje tekst. Primjenom te “formule” možemo tražiti tekst koji ju zadovoljava te kasnije obraditi taj tekst. Recimo, veliku sličnost s regularnim izrazima ima prethodno spomenuto širenje vitičastih zagrada – mi pomoću njih definiramo nekakav tekst, koji prema pravilima širenja vitičastih zagrada generira nekoliko stringova, odnosno, sve stringove koji zadovoljavaju taj izraz koji smo koristili. Ta “formula” se naziva uzorkom, jer prema tom uzorku ćemo vršiti nekakvo pretraživanje, izmjenu teksta i slično.

Za početak, regularni izraz je gotovo uvijek građen od manjih regularnih izraza, a osnovni regularni izraz je jedan znak. Npr, regularni izraz “c” podudara znak “c”, što je zapravo intuitivno.

Kako biste mogli isprobavati regularne izraze (i usput naučili novu naredbu) spomenut ćemo naredbu grep. Njen prvi argument je regularni izraz, a drugi datoteka u kojoj se traži podudaranje. Predlažemo da napravite datoteku primjer.txt u koju ćete upisati, bez navodnika, u prvi redak “Hello World!”, u drugi “Drugi redak” te u treći “Treci redak”. To možete učiniti pomoću tekst editora ili naredbe cat. Naredbu grep ćemo koristiti s opcijom “-E”, koja predstavlja proširenu sintaksu regularnih izraza, koja se ovdje opisuje, pa će naredba imati oblik [grep -E izraz datoteka].

Prve specijalne znakove za regularni izraz koje ćemo spomenuti su uglate zagrade. Pravila za uglate zagrade su zapravo ista kao za spomenuto širenje imena datoteka – dakle, ako želimo podudarati niz od dva znaka, od kojih je prvi malo slovo abecede, a drugi nije znamenka, to možemo postići regularnim izrazom [a-z][^0-9]. Dakle, uloga znakova “^”, “-”, “[“ i “]” je ista kao u širenju. Treba napomenuti da ukoliko želimo neki od tih specijalnih znakova doslovno interpretirati, treba ga staviti na takvo mjesto da nema nejednoznačnosti, npt. ako želimo podudarati znak “]”, moramo ga staviti na početak liste unutar zagrada, jer u suprotnom, interpreter neće znati predstavlja li taj znak sebe ili kraj liste.

Sljedeća dva znaka koja su bitna su “^” i “$”. Prvi označava početak stringa ili retka, a drugi kraj. Bitno je primijetiti da kod regularnih izraza katkad imamo specijalne znakove s više značenja, a poprimaju značenje ovisno o kontekstu. Tako npr. ako se znak “^” nalazi unutar uglatih zagrada, znači isključivanje, dok ako je na početku regularnog izraza pretstavlja početak stringa. Ukoliko ste napravili datoteku primjer.txt prema uputama, možete upisati liniju [grep -E “^[Tr]” primjer.txt]. Na ispisu biste trebali dobiti treći redak datoteke, s označenim mjestom gdje je izraz nađen: na prvom mjestu retku je pronađen znak “T” ili “r”, u ovom slučaju “T”. U prva dva retka datoteke se javlja znak “r”, no on se ne podudara s regularnim izrazom jer nije na početku retka.

Nadalje, imamo oznake granice riječi: “” kraj riječi, “” početak ili kraj riječi i naposlijetku “B” sve osim granice riječi. Upisom linije [grep -E “i <r” primjer.txt] tražite u tekstu znak “i”, iza kojeg je razmak, a zatim slovo r, koje mora biti na početku riječi. Dobit ćete ispis zadnja dva retka datoteke.
Za svaki podizraz se može definirati broj ponavljanja, a to se definira specijalnim znakovima nakon podizraza. Znak “?” dozvoljava najviše jedno ponavljanje, “*” dozvoljava 0 ili više puta (dakle, bilo koji broj ponavljanja), “+” dozvoljava minimalno jednu pojavu (a može i više). Pomoću vitičastih zagrada možemo zadati konkretan broj ponavljanja pa će recimo “{5}” dopustiti točno pet ponavljanja, “{2,}” će dozvoliti 2 ili više ponavljanja, a “{2,5}” 2, 3, 4 ili 5 ponavljanja. Upisom linije [grep -E “l{2}” primjer.txt] dobivate prvi redak, jer su nađena dva znaka “l”, jedan iza drugog.

Podizraze možemo povezivati, što se radi jednostavno navođenjem jednog iza drugog (to smo već učinili više puta, npr. [a-z][0-9] su dva podizraza). Međutim, možemo i primjenjivati operator ILI, na način da dva podizraza odvojimo znakom “|” (AltGr + W). Linijom kôda [grep -E “(l{2})|c” primjer.txt] ćemo tako dobiti prvi i treći redak; u prvom je nađen “ll”, a u trećem “c”. Primjetite zagrade – pomoću njih možete grupirati podizraze, kako bi bili sigurni koji specijalni znak se primjenjuje na koji podizraz.

Ponekad ćete htjeti tražiti neki znak, koji se inače koristi kao specijalni znak. U tom slučaju, možete ga predznačiti s “” te time postići doslovno interpretiranje tog znaka.
Još ćemo spomenuti da znak “.” predstavlja jedan, bilo koji znak. Recimo, ako žečite tražiti baš točku unutar teksta, morat ćete ju predznačiti s “” (kao i bilo koji drugi specijalni znak).
Sada ste imali priliku naučiti najčešće specijalne znakove koji se koriste kod regularnih izraza. Kao što možete vidjeti, oni su vrlo moćan alat pri pretraživanju teksta, što možete činiti naredbom grep. Ovdje opisana sintaksa regularnih izraza je primjenjiva na puno širem području od basha pa tako u nekom tekst editoru ćete pri traženju naletiti na opciju “search for regular expressions” ili slično te tada možete primjenjivati sintaksu pokazanu ovdje, jer je standardna.

Nakon ovog članka možete, kao što vidite, obavljati prilično komplicirane stvari unutar Linuxa, koje bi teško izveli pomoću programa s grafičkim sučeljem. U sljedećem članku vam donosimo kako napraviti vlastitu bash skriptu, koju onda možete bilo kada pokretati bez ponovnog upisivanja kôda te kako se koriste petlje i grananja.

Infobox (): Regularni izrazi
[abc] – slovo a, b ili c
[0-9] – znamenka, bilo koja
[^z] – bilo koji znak, osim slova z
^(regexp) – regexp koji je na početku retka
(regexp)$ – regexp koji je na kraju retka
\<(regexp) – regexp koji je na početku riječi
(regexp)\> – regexp koji je na kraju riječi
\b(regexp) – regexp koji ima granicu riječi ispred sebe
\B(regexp) – regexp koji nema granicu riječi ispred sebe
(regexp)? – najviše jedna pojava regexpa
(regexp)* – 0 ili više pojava regexpan} – točno n pojava regexpa
(regexp)+ – 1 ili više pojava regexpa
(regexp){n} – točno n pojava regexpa
(regexp){n,} – n ili više pojava regexpa
(regexp){n,m} – najmanje n, a najviše m pojava regexpa
(regexp1)|(regexp2) – podudaranje s regexp1 ili regexp2

Autor: Josip Užarević


RELATED ARTICLES

Komentiraj

Please enter your comment!
Please enter your name here

- Advertisment -

Most Popular