2.5        AWK

Linguaggio per trattamento di file sequenziali (1977,1985)

Il nome deriva dalle iniziali dei suoi tre autori (Alfred V. Aho, Peter J. Weinberger, e Brian W. Kernighan). La versione originale nasce agli AT&T Bell Laboratories nel 1977; nel 1985 diventa disponibile, sui sistemi UNIX, una versione più potente con funzioni definibili dall'utente, più fili in input e trattamento delle espressioni regolari.

AWK è indicato per la manipolazione di file sequenziali e per la generazione di stampe; l'interprete gestisce automaticamente l'input ed applica, ad ogni riga di questo, le regole indicate dal programma, per stabilire se questa deve essere scartata, riscritta o modificata. Le regole hanno la forma:

pattern {comando}

dove pattern è una stringa di testo, una condizione o un'espressione regolare: se la riga contiene, il testo, o soddisfa la condizione o l'espressione regolare, è inviata in output se omesso {comando}, altrimenti è eseguito {comando}. Viceversa se manca pattern si esegue {comando}.

La struttura generale di un programma AWK è:

§   regole iniziali (pattern BEGIN)

§   regole applicate ad ogni riga del file di input

§   operazioni finali (pattern END)

Il trattamento dei files è facilitato da un insieme di variabili mantenute dall'interprete: $0 è la riga, $1, $2,… sono i campi della riga, NF è il numero campi nella riga, NR il numero di righe totali, ed altri ancora.

AWK è molto semplice da utilizzare: poiché nella riga di comando si può inserire il "programma", è facile scrivere cose del tipo:

awk '/cpu_time/ {print NR " " $0}' flin

per estrarre dal file flin le righe contenenti cpu_time, indicandone la posizione nel file.

L'esempio seguente realizza la fusione di due file ordinati; la posizione della chiave è la sua lunghezza sono fornite sulla riga di comando (opzioni –v, l'opzione –f introduce lo script AWK):

awk -f merge.awk -v inizio=45 -v lungo=12 flon flin

# GNU Awk 3.0.6

# Il programma fonde due file

#

func take_key(riga) { return sprintf("0%s",substr(riga,inizio,lungo))}

 

func read2() { if ((getline Rec2 < file2) > 0)

                 return take_key(Rec2)

               else

                 return "9"

             }

 

func compare() {while (1 == 1) {

                if (Key1 == Key2) {

                  if (Key1 == "9")

                    break

                }

                if (Key1 < Key2) break

                print Rec2

                Key2 = read2()

                Ck2++

              }

           }

 

BEGIN { print "Inizio fusione"

       file2 = ARGV[2]

       ARGV[2]= "" # il secondo file e' gestito da programma

       print "Chiave a colonna " inizio "  lunga " lungo "\n"

       Key2 = read2()

      }

# corpo del programma

  {

   Key1 = take_key($0)

   compare()

   print $0

  } 

 

END { Key1 = "9"

      compare()

      printf ("\nTotale righe %d (%d + %d)", NR + Ck2, NR, Ck2)

      print "\nFine fusione"}

Le chiavi di input del programma sono:

a.out

a.exe

demounit.gpi

gpc.pas

help.bat

pexecute.pas

prova.pas

unit.txt

flin

flon

merge.awk

p.bat

L'output del programma è:

Inizio fusione

Chiave a colonna 45  lunga 12

 

A        OUT       409,011  04-25-02 10:17p a.out

A        EXE       411,059  04-25-02 10:17p a.exe

DEMOUNIT GPI         4,299  04-25-02 10:17p demounit.gpi

FLIN                   434  05-15-02  6:11p flin

FLON                     0  05-15-02  7:52p flon

GPC      PAS        69,959  07-13-00  4:03a gpc.pas

HELP     BAT            13  05-13-02  6:34p help.bat

MERGE    AWK           956  05-15-02  7:50p merge.awk

P        BAT            58  05-15-02  7:39p p.bat

PEXECUTE PAS         3,887  05-13-02  6:45p pexecute.pas

PROVA    PAS           201  04-25-02 10:17p prova.pas

UNIT     TXT         2,105  04-25-02 10:23p unit.txt

 

Totale righe 12 (4 + 8)

Fine fusione

L'esempio seguente illustra l'utilizzo di AWK per semplificare la scrittura di programmi Brianf (v. par. 2.7) tramite alcune macrofunzioni e l'utilizzo di nomi simbolici per gli indirizzi di memoria.

# GNU Awk 3.0.6

# Macroassembler per Brianf

#

func sost(token) {

   if (token !~ id_regexp) return token;

   if (token in arr_var) return arr_var[token];

   return arr_var[token] = indrz++;

}

func go(to) {

       if (to != "") {

         j = to - mem;

         if (j > 0)

           for (i = 1; i <= j; i++) printf (">");

         else if (j < 0)

           for (i = 0; i > j; i--) printf ("<");

         if (j != 0) mem = to;

        }

      }

func commento() {mtch=1;return sprintf(" \t# %s\n",row);} # commento

func clean(n) {if (n != "") go(n); printf("[-]")}

func incr(n) {go(n);printf("+");}

func decr(n) {go(n);printf("-");}

 

func move(from, to){

          go(from);printf("[-");go(to);printf("+");go(from);printf("]");go(to);

          }

BEGIN {

       id_regexp = "[A-Za-z][A-Za-z0-9]*";

       indrz = 1; # assegna indirizzi a nomi simbolici

       mem = 0;   # posizione di memoria

       mtch = 0;  # indica se trovata istruzione

      }

            {row = $0;$2 = sost($2);$3 = sost($3)} 

/INCR/      {incr($2);printf("%s",commento());}

/DECR/      {decr($2);printf("%s",commento());}

/MOVE/      {clean($3);move($2,$3);printf("%s",commento());}

/INPUT/     {go($2);printf(",%s",commento());}

/OUTPUT/    {go($2);printf(".%s",commento());}

/COPY/      {clean($3);clean("0");go($2);

             printf("[-");incr("0");incr($3);go($2);printf("]");

             move("0",$2);go($3);printf("%s",commento());}

/CLEAN/     {clean($2);printf("%s",commento());}

/GOTO/      {go($2);printf("%s",commento());}

            {if (mtch == 0) print $0; mtch = 0;}  # prende tutto il resto

 

END  {for (VAR in arr_var) printf ("\nVariabile %s \ta %s",VAR, arr_var[VAR]);}