23. december

AWK - pattern scanning and processing language

AWK er beregnet til scanne og behandle tekst inddata, og give tekst som uddata.

AWK bruger "regular expressions" til at matche tekst, og er langt simplere end f.eks. perl, tcl, og basic til disse opgaver. AWK er dog mere avanceret end grep og sed.

AWK er speciel god til opgaver der går ud på at matche tekst, konvertere tekstfiler fra et format til et andet, eventuelt undervejs med optællinger, opsummeringer osv.

AWK er oprindelig designet og implementeret i 1977 af Alfred Aho, Peter Weinberger, and Brian Kernighan fra AT&T Bell Labs. Sidstnævnte fortsætter med at vedligeholde og forbedre AWK.

Den version man normalt stifter kendskab med er gawk som er GNU versionen af awk, og som har en del udvidelser i forholdt til standard AWK. Man skal være opmærksom på at udvidelserne i gawk ikke vil virke med en rigtig standard "awk" som ofte følger med en kommerciel unix. Manualsiden til gawk(1) har et afsnit "GNU EXTENSIONS" som beskriver disse udvidelser. Se også infosiderne med info gawk. Vil man kun anvende kode der følger POSIX standarden så kan man kalde GAWK med "gawk --posix ....".

Ofte er awk på et GNU/linux system blot et symbolsk link til gawk:

lrwxrwxrwx  1 root  root   4 May 30  2000 /usr/bin/awk -> gawk

Awk programmer minder lidt om C i syntaks, og består af en valgfrit begyndelsesblok som udføres før inddata læses:

  BEGIN {
    <kommandoer>
  }

Derefter et antal blokke med en valfri betingelse eller regular expression foran:

 { # uden , så udføres det altid
 }

 <regular expression> {
 }

 <expression> {
 }

Til sidst en valgfri afsluttende blok der udføres efter alt inddata er læst:

 END {
 }

Som standard læses inddata som poster hvor hvert post består af felter adskilt af blanktegn (white space), og linieskift som afslutning på en post. Variable oprettes efter behov, og det afgøres når variablen bruges om det skal fortolkes som et tal eller tekst.

  • Et lille eksempel er ex1.awk :

    # lidt initialisering,
    # egentlig overflødig, da alle nye variable er initielt nul eller blank.
    BEGIN {  linier=0; ord=0; } 
    
    { 
      ord += NF;  # NF = Number of Fields, dvs. antal ord
      linier++;
    }
    
    END {
       print "læst",ord,"ord og",linier,"linier";
       # NR indholder aktuel post nummer, dvs. nu antal poster læst:
       print "NR=",NR;
    }
    

    Man kan kalde f.eks. awk med gawk -f ex1.awk {filnavn(e)}

    Sammenligning resultatet med wc {filnavn(e)} . Wc er programmet (wordcount) der tæller antal tegn, ord, linier mv.

    Det er sådan at de forskellige felter i inddata nummereres fra $1. Variablen NF angiver antallet, og $NF er sidste felt.

  • En enkel opgave er f.eks. at pille en given kolonne ud af noget uddata, som f.eks. optælling af bytes i uddata fra ls -l.

     ls -l |  \
     awk '{ sz += $5; } END { print "bytes=",sz,"bytes", sz/1024,"kbytes"; }'
    

    Det kan også laves som alias i csh/tcsh, eller en kombineret alias og function i bash så man kan lave sin egen "filesize" kommando:

    function filesize_f() {
     ls --color=none -oldG $* | awk '{printf("%8d %s\n",$4,$8);}'
    }
    alias filesize=filesize_f
    

    Denne kan passende placeres i sin bash opstartsfil.

  • En anden opgave er en liste af programmer i ps, men hvor kun selve programnavnet listes:

     ps | awk '{ print $4; }' 
    
  • Nu ville vi måske hellere have alle ens programnavne samlet, og til dette kan man anvende de indbyggede arrays, der indekseres med en tekststreng. Resultat skal sorteres efter hyppighed, og til det anvendes det eksterne program sort:

     ps | \
     awk '{ prog[$4] += 1; } END { for (pr in prog) print prog[pr],pr;}' \
     | sort -r
    
  • Der ønskes en liste af alle almindelige brugere på ens unix system udfra /etc/passwd. Der skal kun vises brugernummer, brugernavn og fuld navn. Der udnyttes at brugernumre i de fleste linux distributioner normalt starter over 500 eller 1000. Da felterne i password filen er adskilt med ":" så anvendes en ny feltadskiller, FS=":". Den kunne også sættes med awk -f ":" når awk kaldes.

    awk ' BEGIN {FS=":";} $3>=500 { print $3" "$1":"$5;}' /etc/passwd
    
  • Vis hvilke grupper brugeren frank er medlem af:

    awk ' BEGIN {FS=":";} $4 ~ /frank/ { print $3,$1}' /etc/group
    
  • Konverter passwordfilen til en kommafil, dvs. CSV fil (comma separated values). En CSV fil kan indlæses af de fleste regneark og databaseprogrammer.

    BEGIN {
     FS=":";
     print "\"login\",\"userID\",\"groupID\",\"name\",\"home\",\"shell\"";
    } 
    { 
     printf("\"%s\",\"%s\",%s,%s,\"%s\",\"%s\",\"%s\"\n",$1,$"x"$3,$4,$5,$6,$7);
    }
    
  • Når vi nu er ved CSV filer, så kan awk også sættes til at læse disse:

    BEGIN { FS="," }
    
    {
      for (i=1; i>=NF; i++) {
            printf("[%s] ",$i);
      }
      print "";
    }
    

    En ulempe i ovenstående er dog at felter i inddata ikke må indeholde "," da det opfattes som skilletegn .

  • Hidtil er der ikke anvendt regular expressions, så her kommer et eksempel med simpel brug af disse. Eksemplet indsætter en tekstfil i et html dokument i mellem linierne med <--! INCL BEGIN --> og <--! INCL END -->. Samtidig konverteres tegnene &, < og > til hhv. &amp;, &lt;, &gt;.
    Programmet kan anvendes til at isætte/opdatere en html fil med en tekst der ændrer sig. Eventuelt kan denne fil være uddata fra et andet program.

    htmlupd :
    #!/bin/sh
    #
    # Eksempel til SSLUG's julekalender den 23 december 2001
    #
    # Programmet indsæter en tekst i en html fil, og konverter samtidig
    # tegn som &, < og > til &amp; &lt; og &gt;
    #
    # Awk skriptet er indbygget i shellscriptet idet den udnytter
    # at  '...' kan fortsætte over flere linier:
    #     gawk '  ...program  '  filnavn
    #
    # Programmet skal dog være mindre end den maksimale kommandolinielængde.
    #
    
    update_html() {
    # kaldes med
    #    update_html  [HTML-FIL] [TEKSTFIL]
    
     if [ -r "$1" -a -r "$2" ] ; then
      gawk -vinclfile="$2" '
      BEGIN { state=1; }
    
      function tohtml(tmpline) {
        # simpel omskrivning af &, < og > til HTML
        gsub(/&/,"\\&amp;",tmpline);
        gsub(/</,"\\&lt;",tmpline);
        gsub(/>/,"\\&gt;",tmpline);
        return tmpline;
      }
    
      function read_incl(fname) {
        #indlæs [fname], konverter tegn til html og udskriv
        while ( (st=(getline < fname))>0 ) {
           print tohtml($0);
        }
      }
    
      /<\!-- INCL BEGIN/ { 
        # har "INCLBEGIN", indsæt  inclfile
        state=2;
        print $0;
        print "<!-- added file=",inclfile," -->" ;
        read_incl(inclfile) ;
        next ; 
      }
    
      /<\!-- INCL END/ {
        # har "INCL END", fortsæt med resten af filen
        state=3;
        print ;  # tom "print" svarer til "print $0"
        next ;
      }
    
      { # kopier inddata, udtagen mellem "INCL BEGIN" og "INCL END"
        if ( state != 2 )  print ;
        next ;
      }
      ' $1
     fi
    }
    
    html=htmlupd.html
    oldhtml=htmlupd.old.html
    tmpincl=htmlupd  # som eksempel brug dette shellscript som inddata
    tmptmp=tmpupd_$$.html
    
    update_html "$html" "$tmpincl"  > "$tmptmp"
    
    if [ -r "$tmptmp"  ] ; then
      mv "$html" "$oldhtml"
      mv "$tmptmp" "$html"
    else
      echo "error, can not read file: $tmptmp"
      echo "no files changed"
    fi
    
    htmlupd.html :
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
            "http://www.w3.org/TR/REC-html40/loose.dtd">
    <html> 
    <head>
     <title>Eksempel til Julekalender 23. dec 2001</title>
    </head>
    <body style="background-color:white; color:steelblue;">
    <h3>Eksempel til SSLUG's Julekalender 23. dec 2001</h3>
    
    <pre style="color:#000033; background-color:#EEEEDD;">
    <!-- INCL BEGIN -->
    <!-- INCL END -->
    </pre>
    
    </body>
    </HTML>