AWK skripta za pretrazivanje reci iz recnika

AWK skripta za pretrazivanje reci iz recnika

offline
  • soxxx 
  • Prijatelj foruma
  • Pridružio: 25 Maj 2005
  • Poruke: 1482
  • Gde živiš: Gracanica, Kosovo

Vi koji koristite *nix sisteme ste verovatno vec videli da postoji tzv. Linux recnik za Srpsko-Engleski (i obratno):

http://www.mycity.rs/Linux-Download/Srpski-Recnik-za-Linux.html

Ovde cemo probati da napisemo skript koji ce raditi prakticno isto - pronaci zeljenu rec i ispisati je na ekranu. Jedina razlika je sto cemo ovde koristiti awk umesto C jezika. Cilj je prikazivanje koriscenja awk jezika za pretragu fajlova i formatiranje izlaza.
Za pracenje je dovoljno osnovno poznavanje awk jezika, tako da se necemo mnogo osvrtati na upoznavanje sa samim jezikom.

Odmah na rad, pocecemo sa jednostavnim pretrazivanjem fajla:

awk '/greska/' reci.dat

(Ovo inace predstavlja 'awk idiom', tj. awk nacin za skracivanje komandi. Gornja komanda je isto sto i: awk '{if($0 ~ /greska/) print}')

Izlaz komande je:

$ awk '/greska/' reci.dat

En#Delinquency|Greska-Krivica-Zlocin-Propust-Pogreska-Prestup
En#Error|Greska-Pogreska-Zabluda-Neispravnost-Greh
En#Lapse|Prestajanje-Gasenje-Odstupanje-Pogreska-Zaostati-Otpasti-Nestati-Proticanje-Grska-Greh-Omaska-Propadanje-Propustanje
En#Mistake|Zabluda-Zabuniti Se-Propust-Prevariti Se-Pobrkati-Pogresiti-Greska-Nesporazum-Pogreska-Rdjavo Suditi-Zabuna
Sr#Pogreska|Error-Lapse-Delinquency-Mistake


Koristimo istu bazu reci kao i serbdict Linux recnik (obican tekstualni fajl), i format je sledeci:

Jezik   Rec   Granicnik   Prevod reci
Eng#   Error       |       Error-Lapse-Delinquency-Mistake


Prvo uocavamo da se prevod reci "Error" pojavljuje dva puta, jednom kao prevod reci sa engleskog, i drugi put u samom prevodu. U awk-u podrazumevani razdvajac izmedju polja je ' ', tj. prazno polje.
Izmenom podrazumevanog razdvajaca (FS - field separator), opcijom "-F" u komandnoj liniji ili menjanjem FS varijable u BEGIN bloku, vrsimo pretragu reci po prvom polju i postizemo sledece:

$ awk -F "|" '$1 ~ /greska/' reci.dat

Sr#Pogreska|Error-Lapse-Delinquency-Mistake


Sada ce awk traziti rec samo u prvom delu, a ne u prevodu. Ali hajde da malo "ulepsamo" izlaz komande i dodamo par korisnih informacija. Posto dodajemo vise komandi, zbog preglednosti stavljamo sve u jedan fajl, a kasnije pozivamo taj fajl koristeci "-f" opciju.
Iskoristicemo i mogucnost da se navede vise podrazumevanih razdvajaca i tako uklonimo Eng# i Sr# sa pocetka stringa.

BEGIN {
   FS = "[#|]"
}
{
   if($2 ~ /greska/) {
      printf("%-48s %-50s\n", $2, $3)
   }
}


U awk-u, BEGIN blok se izvrsava pre nego se ucita ijedan fajl. Postoji i END blok koji se izvrsava jednom kad se zavrsi sa obradom svih unosa i njegovo koriscenje cemo videti kasnije. Primeticete da sada pretrazujemo po drugom polju, to je sbog toga sto sada awk "lomi" svaki unos na 3 polja - to je zato sto smo ubacili dva podrazumevana razdvajaca. Awk vidi unos ovako:


razdvajac:       FS    FS
string:       Eng#Error|Error-Lapse-Delinquency-Mistake
              \_/ \___/  \___________________________/
polja:         $1   $2                  $3


Sa ovom awk skriptom imamo sledeci izlaz:

$ awk -f test.awk reci.dat

Pogreska                                         Error-Lapse-Delinquency-Mistake             


Sledece, recimo da zelimo da izbrojimo koliko prevoda za odabranu rec smo pronasli. Potrebne su nam samo manje izmene:

BEGIN {
   FS = "[#|]"
}
{
   if($2 ~ /Room/) {
      printf("%-48s %-50s\n", $2, $3)
      c++
   }
}
END {
   printf("\n\tNadjeno je: %d prevoda.\n", c)
}


$ awk -f test.awk reci.dat

Ante Room                                        Predsoblje                                       
Ante-Room                                        Predsoblje                                       
Bed-Room                                         Spavaca Soba                                     
  ... skracen izlaz zbog citljivosti ...                           
Smoking-Room                                     Soba Za Pusace                                   
Spare Room                                       Gostinska Soba                                   
State Room                                       Svecana Prosto.                                   
State-Room                                       Svecana Prosto.                                   
Strong Room                                      Trezor                                           
Waiting Room                                     Cekaonica                                         
Ward Room                                        Biraliste             

        Nadjeno je: 40 prevoda.


Dalje mozemo malo ulepsati formatiranje dodavanjem linija izmedju prevoda, i ispisivanjem hedera na pocetku:
BEGIN {
   FS = "[#|]"
   printf "\n"
   print "========================================================================================================="
   printf("| %-48s | %-50s | \n", "Trazena rec", "Prevod reci")
   print "========================================================================================================="
}
{
   if($2 ~ /Room/) {
      printf("| %-48s | %-50s |\n", $2, $3)
      print "---------------------------------------------------------------------------------------------------------"
      c++
   }
}
END {
   printf("\n\tNadjeno je: %d prevoda.\n", c)
}


$ awk -f test.awk reci.dat

=========================================================================================================
| Trazena rec                                      | Prevod reci                                        |
=========================================================================================================
| Ante Room                                        | Predsoblje                                         |
---------------------------------------------------------------------------------------------------------
| Ante-Room                                        | Predsoblje                                         |
---------------------------------------------------------------------------------------------------------
| Bed-Room                                         | Spavaca Soba                                       |
---------------------------------------------------------------------------------------------------------
   ... skracen izlaz zbog citljivosti ...
---------------------------------------------------------------------------------------------------------
| Waiting Room                                     | Cekaonica                                          |
---------------------------------------------------------------------------------------------------------
| Ward Room                                        | Biraliste                                          |
---------------------------------------------------------------------------------------------------------

        Nadjeno je: 40 prevoda.



Sada dolazimo do malog problema, posto smo ceo kod prebacili u skriptu, svaki put kada zelimo da pronadjemo neku rec moramo da menjamo skriptu. Mozemo da awk skriptu "zavijemo" u shell skriptu, ili da probamo da menjamo argumente koje zadajemo awk skripti. Skoljke ionako ne volim da jedem pa idemo da drugu opciju. Wink U pomoc nam priskacu awk ugradjene varijable ARGC i ARGV (onima koji poznaju recimo C ce ovo biti poznato). ARGC varijabla sadrzi broj argumenata sa kojima je awk pokrenut, a ARGV niz sadrzi argumente. ARGV[0] je uvek ime awk programa, ili ime skripte, u zavisnosti kako je skripta pokrenuta, i od toga na kom se sistemu koristi. Mali primer kako bi ovo bilo jasnije:

$ cat test.awk                         
BEGIN {
        printf "Broj argumenata: %d\n\n", ARGC
        for(i=0; i<ARGC; i++)
                printf "ARGV[%d] => %s\n", i, ARGV[i]
}

$ awk -f test.awk reci.dat             
Broj argumenata: 2

ARGV[0] => awk
ARGV[1] => reci.dat

$ awk -f test.awk reci.dat jedan dva tri
Broj argumenata: 5

ARGV[0] => awk
ARGV[1] => reci.dat
ARGV[2] => jedan
ARGV[3] => dva
ARGV[4] => tri

$ sed -i '1i\#!/usr/bin/awk -f' test.awk
$ chmod 0777 test.awk
$ ./test.awk
Broj argumenata: 1

ARGV[0] => gawk

$ ./test.awk jedan dva
Broj argumenata: 3

ARGV[0] => /usr/bin/awk
ARGV[1] => jedan
ARGV[2] => dva

Sa ovim znanjem, i pretpostavkom da je kokoska starija od jajeta, mozemo izmeniti awk skriptu tako da prvi argument bude trazena rec, i unecemo putanju do baze reci u samu skriptu. Iskoristicemo i BEGIN blok kako bi kreirali sve "crtice, prazna polja i zezancije" kako bi ucinili skriptu malo citljivijom, i odvojicemo prevode za reci sa Srpskog i Engleskog, kao i uvesti posebne brojace za oba.

BEGIN{
   FS = "[#|]"   
   findMe = ARGV[1]
   ARGC--
   ARGV[ARGC++] = "./reci.dat"
}

{   if($2 ~ findMe) {
      if($0 ~ /^Sr#/){
         csr++
         ser[$2] = $3
      }else{
         cen++
         eng[$2] = $3
      }   
   }
}
function PrintHeader(){
      print "\n"
      print "========================================================================================================="
      printf("| %-48s | %-50s | \n", "Trazena rec", "Prevod reci")
      print "========================================================================================================="
}

END{
   if(!cen)
      print "\n\tNema reci na engleskom."
   else {
      PrintHeader()
      for(i in eng) {
         gsub(/-/, ", ", eng[i])
         printf("| %-48s | %-50s |\n", i, eng[i])
         print "---------------------------------------------------------------------------------------------------------"
      }
      print "\n"
      printf("\tNadjeno: %d prevoda sa engleskog.\n", cen)
   }
   
   if(!csr)
      print "\n\tNema reci na srpskom."
   else {
      PrintHeader()
      for(j in ser){
         gsub(/-/, ", ", ser[j])
         printf("| %-48s | %-50s |\n", j, ser[j])
         print "---------------------------------------------------------------------------------------------------------"
      }
      print "\n"
      printf("\tNadjeno: %d prevoda sa srpskog.\n", csr)
    }
}

Hajde da proverimo sta smo postigli:

$ ./test.awk Kurs

        Nema reci na engleskom.


=========================================================================================================
| Trazena rec                                      | Prevod reci                                        |
=========================================================================================================
| Vecernji Kursev                                  | Night, School                                      |
---------------------------------------------------------------------------------------------------------
| Kurs                                             | Standard, Course, Quotation, Market, Policy, Route, Rate |
---------------------------------------------------------------------------------------------------------
| Obracunski Kurs                                  | Parity                                             |
---------------------------------------------------------------------------------------------------------
| Kurs (Broda)                                     | Course                                             |
---------------------------------------------------------------------------------------------------------
| Kursum                                           | Bullet                                             |
---------------------------------------------------------------------------------------------------------


        Nadjeno:  5 prevoda sa srpskog.

U gornjoj skirpti mogli smo jednostavno napisati:
Umesto:

   findMe = ARGV[1]
   ARGC--
   ARGV[ARGC++] = "./reci.dat"

Stavljamo:

   findMe = ARGV[1]
   ARGV[1] = "./reci.dat"

To smo jednostavno uradili (dobro, ja sam uradio) kako bi pokazali da su manipulazije, mahinacije i malverzacije sa ARGC i ARGV u BEGIN bloku dozvoljene.
Ako se sama skripta pokrene bez argumenata, tj. zadate reci, nista se nece desiti. Kako bi smo ovo sprecili na pocetku skripte dodajemo proveru dali postoji zadata rec, i onda nastavljamo. Za to ce nam posluziti ARGC varijabla, koja, secate se, sadrzi broj argumenata sa kojima je skripta pokrenuta (od koje je sam program/skripta jedan od njih, samim tim ARGC je uvek minimalno 1). Ako nema argumenata, ispisujemo "upozorenje", pokazujemo zuti karton i prekidamo skriptu. Linija koja nam je potrebna u BEGIN bloku je:

   if(ARGC != 2){ print "Unesite trazenu rec"; exit }

Zasto bas u BEGIN bloku? Zato jer trebamo izvrsiti proveru pre bilo kakvog rada nad unosom.
Ako pokusamo da pokrenemo skriptu bez zadate reci, desava se sledece:

$ ./test.awk     
Unesite trazenu rec!

        Nema reci na engleskom.

        Nema reci na srpskom.


WTF?! Zasto se skripta odmah ne prekida nego imamo poruku da nema trazenih reci u recniku?! Aha, END blok se izvrsava nakon obrade unosa, BEZ obzira na to dali je ista nadjeno! Zato nam se ispisuju dve printf() linije. Uvodimo jos jednu varijablu inspirativnog naziva, i nakon toga proveravamo vrednost te varijable u END bloku; ako je 0 nastavljamo, ako je 1 prekidamo skriptu odmah i sad.

Citaoci sa boljim vidom ce verovatno primetiti da ce odredjeni prevodi "strciti", kao u gornjem primeru sa rec "Kurs". To jos i nije problem, prevod za rec "Account" ima 252 karaktera ... Very Happy Problem mozemo resiti na dva nacina;
jedan je da jednostavno ne obracamo paznju na tu anomaliju,
a drugi je da napisemo funkciju koja ce meriti duzinu prevoda, i ako je duzina veca od 50 karaktera onda prelamamo prevod i ispisujemo je u novom redu.
Iz nepoznatih razloga odlucujemo se za drugu opciju:

function ckL(word){
   if(length(word)<=50)
      return word substr(spaces, length(word))    
   else{
      wordPart = substr(word,51)
      return substr(word,1,50)" | \n| " spaces "| " ckL(wordPart)
   }
}


Znaci, ako je duzina stringa manja od, ili tacno 50 karaktera, vracamo string i tacno koliko nam je praznih polja potrebno (oduzimamo duzinu stringa od praznih polja).
Ako je string duzi od 50 karaktera, odsecamo prvih 50 karaktera i cuvamo ostatak u varijabli wordPart. Onda vracamo prvih 50 karaktera stringa, novu liniju, prazna polja i rekurzivno proveravamo ostatak stringa.

Sta jos mozemo uciniti kako bi skripta bila malo citljivija? Ove crtice su simpa na izlazu, ali u skripti izgledaju bezMeze. Oki, probacemo da iskoristimo sprintf() kako bi smanjili broj potrebnih karaktera, i uz pomoc gsub() promenimo prazna polja u potrebne znakove:

   spaces = sprintf("%48s", "");
   dashes = dashes2 = sprintf("%105s", "")
      gsub(/ /, "-", dashes); gsub(/ /, "=", dashes2)


Ako sada dodamo proveru verijable kako bi smo onemogucili stampanje iz END bloka, i funkciju za proveru duzine prevoda, dobijamo sledecu skriptu u konacnom obliku (ah, napokon!):


#!/bin/awk -f
BEGIN{
   if(ARGC != 2){ print "\nUnesite trazenu rec!\n"; qrac=1; exit }

   FS = "[#|]"   
   spaces = sprintf("%49s", "");
   dashes = dashes2 = sprintf("%105s", "")
      gsub(/ /, "-", dashes); gsub(/ /, "=", dashes2)

   findMe = ARGV[1]
   ARGV[1] = "./reci.dat"
}

{   if($2 ~ findMe) {
      if($0 ~ /^Sr#/){
         csr++
         ser[$2] = $3
      }else{
         cen++
         eng[$2] = $3
      }   
   }
}
function PrintHeader(){
      print "\n"
      print dashes2
      printf("| %-48s | %-50s | \n", "Trazena rec", "Prevod reci")
      print dashes2
}

function ckL(word){
   if(length(word)<=50)
      return word substr(spaces, length(word))    
   else{
      wordPart = substr(word,51)
      return substr(word,1,50)" | \n| " spaces "| " ckL(wordPart)
   }
}

END{   
   if(!qrac){   
      if(!cen)
         print "\n\tNema reci na engleskom."
      else {
         PrintHeader()
         for(i in eng) {
            gsub(/-/, ", ", eng[i])
            printf("| %-48s | %-48s |\n", i, ckL(eng[i]))
            print dashes
         }
         print "\n"
         printf("\tNadjeno: %d prevoda sa engleskog.\n", cen)
      }
   
      if(!csr)
         print "\n\tNema reci na srpskom."
      else {
         PrintHeader()
         for(j in ser){
            gsub(/-/, ", ", ser[j])
            printf("| %-48s | %-48s |\n", j, ckL(ser[j]))
            print dashes
         }
         print "\n"
         printf("\tNadjeno: %d prevoda sa srpskog.\n", csr)
       }
   }   print "\n"
}

I primer za primer:

$ ./test.awk Slucaj

        Nema reci na engleskom.


=========================================================================================================
| Trazena rec                                      | Prevod reci                                        |
=========================================================================================================
| U Svakom Slucaju                                 | Anyhow                                             |
---------------------------------------------------------------------------------------------------------
| Srecan Slucaj                                    | Squeak                                             |
---------------------------------------------------------------------------------------------------------
| U Slucaju Da                                     | In The Event, Provided That                        |
---------------------------------------------------------------------------------------------------------
| Slucaj                                           | Hap, Accident, Occurence, Precendent, Hit, Inciden |
|                                                  | t, Occasion, Occurrence, Emergency, Random, Contin |
|                                                  | gency, Circumstance, Event, Chance, Case, Accidenc |
|                                                  | e, Casualty, Haphazard, Incidence, Luck, Precedent |
---------------------------------------------------------------------------------------------------------
| Slucajnost                                       | Randomness, Contingency, Fluke, Chance             |
---------------------------------------------------------------------------------------------------------
| Smrtni Slucaj                                    | Death                                              |
---------------------------------------------------------------------------------------------------------
| Nesrecan Slucaj                                  | Misadventure, Casualty, Mishap                     |
---------------------------------------------------------------------------------------------------------
| Hitan Slucaj                                     | Emergency                                          |
---------------------------------------------------------------------------------------------------------
| U Slucaju Ako                                    | Provided                                           |
---------------------------------------------------------------------------------------------------------
| Slucajan                                         | Casual, Promiscuous, Accidental, Odd, Occasional,  |
|                                                  | Haphazard, Incident, Incidental, Accessory, Advent |
|                                                  | itious, Fortuitous, Fortutous, Random, Scratch, Pa |
|                                                  | tchy, Chance                                       |
---------------------------------------------------------------------------------------------------------
| Nesretan Slucaj                                  | Accident                                           |
---------------------------------------------------------------------------------------------------------
| Slucajno                                         | Perchance, Occasionally, Randomly, Haphazard       |
---------------------------------------------------------------------------------------------------------
| Slucajno Cuti                                    | Overhear                                           |
---------------------------------------------------------------------------------------------------------
| Cudan Slucaj                                     | Oddity                                             |
---------------------------------------------------------------------------------------------------------


        Nadjeno: 14 prevoda sa srpskog.



Kao sto vidite, u prevodu se moze desiti da se rec "prelomi" na sledeci red, a i reci pretrazujemo sa prvim velikim slovom (zato sto su tako unesene u tekstualni fajl). Drugi problem je trivijalno resiti, a i prvi se moze izmozgati.
Ali, to ostavljamo kao domaci zadatak za one koji su hrabri i avanturistickog duha. Wink

Uzdravlje Ziveli



Registruj se da bi učestvovao u diskusiji. Registrovanim korisnicima se NE prikazuju reklame unutar poruka.
offline
  • bocke  Male
  • Moderator foruma
  • Glavni moderator Linux foruma
  • Veliki Pingvin
  • Guru
  • Pridružio: 16 Dec 2005
  • Poruke: 12319
  • Gde živiš: Južni pol

Mislim da bi bilo pametno napisati kratki tutorijal za awk. Smile Recimo da je ovo već malo "napredniji" primer.

Imam 2 predloga:
1) da neko napiše tutorijal,
2) da neko prevede tutorijal sa engleskog (ako to dozvoljava licenca tutorijala)

Čini mi se da bi bio najbolji kratak i koncizan tutorijal koji ne prelazi 3-4 štampane strane.



Ko je trenutno na forumu
 

Ukupno su 652 korisnika na forumu :: 33 registrovanih, 3 sakrivenih i 616 gosta   ::   [ Administrator ] [ Supermoderator ] [ Moderator ] :: Detaljnije

Najviše korisnika na forumu ikad bilo je 3466 - dana 01 Jun 2021 17:07

Korisnici koji su trenutno na forumu:
Korisnici trenutno na forumu: _Petar, _Rade, A.R.Chafee.Jr., aleksmajstor, Botovac, BraneS, celeron, Drug pukovnik, dule10savic, Dusko Nikolin, Faki-Valjevo, ivica976, Libertas, ljuba, Marko Marković, mercedesamg, Mixelotti, Nebo_M, nedeljkovici, nuke92, pjaka2001, pristinski korpus, Profica, Rakenica, rovac, royst33, Sale.S, Sirius, Smiljke, Snorks, suton, Vlad000, 1107