AWK

Το AWK είναι εργαλείο ανάλυσης δεδομένων και παραγωγής αναφορών και χρησιμοποιεί μια γλώσσα σεναρίων που λειτουργεί με βάση τα δεδομένα εισόδου (data-driven), η οποία αποτελείται από ενέργειες που πρέπει να εκτελεστούν σε δεδομένα τύπου κειμένου (σε αρχεία ή ροές δεδομένων), με σκοπό την παραγωγή αναφορών με κάποια μορφοποίηση. Η γλώσσα που χρησιμοποιείται από το εργαλείο awk χρησιμοποιεί κυρίως τύπους δεδομένων συμβολοσειρών (strings), συσχετιστικών πινάκων (associative arrays, πίνακες των οποίων οι δείκτες είναι τύπου συμβολοσειράς) και κανονικές εκφράσεις (regular expressions).
Η γλώσσα AWK αποτέλεσε ένα από τα πρώτα εργαλεία που εμφανίστηκαν στην Έκδοση 7 του Unix (Version 7 Unix) και έγινε δημοφιλής σαν τρόπος να εισαχθούν υπολογιστικές λειτουργίες σε μια σωλήνωση του (pipeline) του Unix. Σχεδόν κάθε σύγχρονο λειτουργικό σύστημα τύπου Unix που είναι διαθέσιμο σήμερα περιλαμβάνει τη γλώσσα AWK. Η AWK αναφέρεται στο Single UNIX Specification σαν υποχρεωτικό εργαλείο ενός λειτουργικού συστήματος Unix και αποτελεί μαζί με το κέλυφος Bourne τις δύο μοναδικές γλώσσες σεναρίων που είναι κανονικά διαθέσιμες σε ένα περιβάλλον Unix. Ανήκει επίσης στις εντολές που απαιτούνται από την προδιαγραφή Linux Standard Base. Υπάρχουν επίσης διαθέσιμες υλοποιήσεις της AWK για πολλά άλλα λειτουργικά συστήματα.
Η AWK δημιουργήθηκε στα Bell Labs στα τέλη της δεκαετίας του 1970, και το όνομά της προέρχεται από τα επίθετα των δημιουργών της — Alfred Aho, Peter Weinberger και Brian Kernighan. Το awk με μικρά γράμματα αναφέρεται στο αντίστοιχο πρόγραμμα του Unix ή του Plan 9 που εκτελεί προγράμματα γραμμένα στη γλώσσα AWK.
Η ισχύς, η συντομία, αλλά και οι περιορισμοί των πρώτων προγραμμάτων σε AWK ενέπνευσαν το Larry Wall να γράψει την Perl, ενώ ταυτόχρονα αναπτυσσόταν μια νέα, ισχυρότερη POSIX AWK και η gawk (GNU AWK). Αν και η AWK και ο sed είχαν σχεδιαστεί να υποστηρίζουν προγράμματα της μιας γραμμής, ακόμα και οι πρώτοι χρήστες της AWK από τα Bell Labs συχνά έγραφαν καλά δομημένα προγράμματα μεγάλου μεγέθους σε AWK. Παρά τον περιορισμένο σκοπό για τον οποίο προορίζεται, η AWK είναι Τιούρινγκ-πλήρης (Turing-complete).

Δομή των προγραμμάτων σε AWK

Κατά τον Alfred V. Aho:
Η AWK είναι μια γλώσσα για επεξεργασία αρχείων κειμένου. Κάθε αρχείο χρησιμοποιείται σαν μια ακολουθία από εγγραφές, και εξορισμού κάθε γραμμή είναι μια εγγραφή. Κάθε γραμμή σπάει σε πεδία, επομένως μπορούμε να θεωρήσουμε ότι η πρώτη λέξη μιας γραμμής είναι το πρώτο πεδίο, η δεύτερη λέξη είναι το δεύτερο πεδίο, κλπ. Ένα πρόγραμμα σε AWK αποτελείται από μιας ακολουθία από εντολές της μορφής πρότυπο-ενέργεια. Η AWK διαβάζει την είσοδο γραμμή προς γραμμή. Μια γραμμή σαρώνεται για κάθε πρότυπο του προγράμματος, και για κάθε πρότυπο που ταιριάζει, εκτελείται η αντίστοιχη ενέργεια.
Ένα πρόγραμμα σε AWK είναι μια σειρά από ζεύγη πρότυπο-ενέργεια, που γράφονται ως εξής:
συνθήκη { ενέργεια }
όπου η συνθήκη είναι συνήθως μια έκφραση και η ενέργεια μια σειρά από εντολές.
Η είσοδος σπάει σε εγγραφές (records), οι οποίες εξορισμού θεωρείται ότι χωρίζονται από χαρακτήρα αλλαγής γραμμής, επομένως η είσοδος σπάει σε γραμμής. Το πρόγραμμα δοκιμάζει κάθε γραμμή έναντι σε κάθε μια από τις συνθήκες με τη σειρά, και εκτελεί την ενέργεια για κάθε έκφραση που είναι αληθής.
Να σημειωθεί ότι η συνθήκη ή η ενέργεια μπορεί να παραλειφθεί. Σε αυτές τις περιπτώσεις, η προεπιλεγμένη συνθήκη ταιριάζει σε κάθε εγγραφή και η προεπιλεγμένη ενέργεια τυπώνει την εγγραφή.
Εκτός από απλές εκφράσεις της AWK, όπως η foo == 1 ή η /^foo/, η συνθήκη μπορεί να είναι BEGIN ή END, που έχουν σαν αποτέλεσμα η ενέργεια να εκτελείται πριν ή μετά από το διάβασμα όλων των εγγραφών, ή πρότυπο1, πρότυπο2 που ταιριάζει με το εύρος των εγγραφών που αρχίζουν με μια εγγραφή που ταιριάζει με το πρότυπο1 μέχρι και την εγγραφή που ταιριάζει με το πρότυπο2, πριν συνεχίσει πάλι την αναζήτηση για την επόμενη γραμμή που ταιριάζει με το πρότυπο1.
Οι εκφράσεις της AWK μπορούν να έχουν αριθμητικούς και λογικούς τελεστές, καθώς και τον τελεστή ~, που δοκιμάζει να ταιριάξει μια κανονική έκφραση σε μια συμβολοσειρά. Υπάρχει επίσης η βολική σύνταξη /regexp/, η οποία δοκιμάζει να ταιριάξει με την τρέχουσα εγγραφή χωρίς τη χρήση του τελεστή ~.

Εντολές της AWK

Οι εντολές της AWK εισάγονται στη θέση της ενέργειας που αναφέρθηκε παραπάνω. Οι εντολές της AWK μπορούν να είναι κλήσεις συναρτήσεων, αναθέσεις μεταβλητών, υπολογισμοί, ή οποιοσδήποτε συνδυασμός αυτών. Η AWK περιλαμβάνει υποστήριξη για πολλές συναρτήσεις, ενώ διάφορες εκδόσεις της προσθέτουν επιπλέον συναρτήσεις, καθώς επίσης και δυναμικά συνδεδεμένες βιβλιοθήκες (dynamically linked libraries), οι οποίες προσφέρουν ακόμα περισσότερες συναρτήσεις.
Για συντομία στα παρακάτω παραδείγματα θα παραλείπονται οι αγκύλες ( { } ) που περιέχουν τις εντολές.

Μεταβλητές και σύνταξη

Το ονόματα των μεταβλητών μπορούν να χρησιμοποιούν οποιονδήποτε από τους χαρακτήρες [A-Za-z0-9_] αλλά δε μπορούν να είναι λέξεις-κλειδιά της γλώσσας. Οι τελεστές + - * / αναπαριστούν την πρόσθεση, την αφαίρεση, τον πολλαπλασιασμό και τη διαίρεση αντίστοιχα. Όταν δύο μεταβλητές (ή σταθερές συμβολοσειρές) τοποθετούνται η μια δίπλα στην άλλη, αυτό δηλώνει τη συνένωσή τους. Οι σταθερές συμβολοσειρές οριοθετούνται από διπλά εισαγωγικά. Οι εντολές δεν χρειάζεται να τερματίζονται με ελληνικά ερωτηματικά (semicolons). Τέλος, σχόλια μπορούν να προστεθούν σε ένα πρόγραμμα σαν γραμμές που αρχίζουν με το χαρακτήρα #.

Συναρτήσεις που ορίζονται από το χρήστη

Οι συναρτήσεις ορίζονται παρόμοια με τη C, με τη λέξη-κλειδί function, ακολουθούμενη από το όνομα της συνάρτησης, τα ονόματα των παραμέτρων της και το σώμα της συνάρτησης. Για παράδειγμα:
function add_three (number) {
  return number + 3
}
Η συνάρτηση αυτή μπορεί να κληθεί ως εξής:
print add_three(36)     # Εμφανίζει 39
Οι συναρτήσεις μπορούν να έχουν μεταβλητές με τοπική εμβέλεια (scope): τα ονόματά τους τοποθετούνται στο τέλος της λίστας των παραμέτρων, αν και δεν τους δίνεται τιμή όταν καλείται η συνάρτηση. Συνηθίζεται να γράφεται κάποιο κενό στη λίστα των παραμέτρων πριν τις τοπικές μεταβλητές, για να ξεχωρίζουν από τις κανονικές μεταβλητές.
Μπορεί να υπάρχει κενό στον ορισμό της συνάρτησης, μεταξύ του ονόματος της συνάρτησης και της αριστερής παρένθεσης, αλλά δεν επιτρέπεται τέτοιο κενό κατά την κλήση της συνάρτησης.

Γειά σου, κόσμε

Το κλασικό παράδειγμα "Γειά σου, κόσμε" ("Hello world") γράφεται σε AWK ως εξής:
BEGIN {
    print "Γειά σου, κόσμε!"
    exit(0)
}
Το μόνο πρότυπο είναι το BEGIN και περιέχει ως ενέργεια την εντολή εκτύπωσης της συμβολοσειράς "Γειά σου, κόσμε", επομένως η εντολή αυτή θα εκτελεστεί κατά την εκκίνηση του προγράμματος και πριν διαβαστεί οποιοδήποτε input. Η εντολή exit χρειάζεται εδώ, ώστε το πρόγραμμα να μην προχωρήσει σε περαιτέρω επεξεργασία του input.

Εμφάνισε γραμμές μεγαλύτερες από 80 χαρακτήρες

Τυπώνει όλες τις γραμμές που έχουν μήκος μεγαλύτερο από 80 χαρακτήρες. Η προεπιλεγμένη ενέργεια είναι η εμφάνιση της τρέχουσας γραμμής.
length($0) > 80

Εμφανίζει τον αριθμό των λέξεων

Μετρά τις λέξεις της εισόδου και εμφανίζει τον αριθμό των γραμμών, των λέξεων και των χαρακτήρων (όπως το wc):
{
    w += NF
    c += length($0) + 1
}
END { print NR, w, c }
Δεν υπάρχει πρότυπο για την πρώτη ενέργεια του προγράμματος, επομένως ταιριάζει κάθε γραμμή της εισόδου και οι ενέργειες που αυξάνουν τους μετρητές εκτελούνται σε κάθε γραμμή. Η έκφραση w += NF είναι συντομογραφία της w = w + NF.

Σύνολο τελευταίων λέξεων

{ s += $NF }
END { print s + 0 }
Η s αυξάνεται με την αριθμητική τιμή της $NF που είναι η τελευταία λέξη της γραμμής (όπως ορίζεται από το διαχωριστή πεδίων της AWK, που έχει την προκαθορισμένη τιμή του λευκού κενού). Η NF είναι ο αριθμός των πεδίων της τρέχουσας γραμμής, π.χ. 4. Επειδή $4 είναι η τιμή του τέταρτου πεδίου, η $NF δίνει πάντα την τιμή του τελευταίου πεδίου μιας γραμμής. Ο τελεστής $ παίρνει ένα όρισμα και έχει την υψηλότερη προτεραιότητα (operator precedence). Αν μια γραμμή δεν έχει πεδία, τότε η NF είναι 0, $0 είναι ολόκληρη η γραμμή, που είναι άδεια (εκτός από τα κενά που μπορεί να περιέχει), επομένως η αριθμητική τιμή τότε είναι 0.
Στο τέλος της εισόδου, το πρότυπο END ταιριάζει και τυπώνεται η s. Αν δεν έχουν υπάρξει γραμμές εισόδου, δε θα έχει δοθεί ποτέ τιμή στην s, η οποία και θα είναι η κενή συμβολοσειρά. Η πρόσθεση του μηδέν σε μια συμβολοσειρά αποτελεί ιδίωμα της AWK που εξαναγκάζει μια συμβολοσειρά να γίνει αριθμητική τιμή (coercion). Αντίθετα, η συνένωση με κενή συμβολοσειρά εξαναγκάζει έναν αριθμό να γίνει συμβολοσειρά, π.χ. s "".  (Δεν υπάρχει τελεστής συνένωσης συμβολοσειρών, απλά αυτές τοποθετούνται η μια δίπλα στην άλλη.) Λόγω του εξαναγκασμού το πρόγραμμα τυπώνει 0 για κενή είσοδο, αλλιώς θα τύπωνε μια κενή γραμμή.

Ταίριασμα με ένα εύρος από γραμμές εισόδου

$ yes Wikipedia | awk 'NR % 4 == 1, NR % 4 == 3 { printf "%6d  %s\n", NR, $0 }' | sed 7q
     1  Wikipedia
     2  Wikipedia
     3  Wikipedia
     5  Wikipedia
     6  Wikipedia
     7  Wikipedia
     9  Wikipedia
$
Η εντολή yes τυπώνει συνεχόμενα το όρισμά της (εξορισμού το γράμμα "y") σε μια γραμμή. Σε αυτό το παράδειγμα, η εντολή τυπώνει τη λέξη "Wikipedia". Η εντολή της ενέργειας τυπώνει τις γραμμές αριθμημένες. Η συνάρτηση printf προσομοιώνει την printf της C, και λειτουργεί με παρόμοιο τρόπο με την εντολή print που έχει προαναφερθεί. Το πρότυπο προς ταίριασμα λειτουργεί ως εξής: NR είναι ο αριθμός των εγγραφών, συνήθως οι γραμμές εισόδου, που έχει ήδη διαβάσει η AWK, δηλαδή ο τρέχων αριθμός γραμμής που αρχίζει από το 1 για την πρώτη γραμμή της εισόδου. Ο % είναι ο τελεστής υπολοίπου από διαίρεση (modulo). Η NR % 4 == 1 είναι αληθής για την πρώτη, την πέμπτη, την ένατη, κλπ, γραμμές της εισόδου ενώ η NR % 4 == 3 είναι αληθής για την τρίτη, την έβδομη, την ενδέκατη, κλπ, γραμμές της εισόδου. Το πρότυπο του εύρους είναι ψευδές μέχρι να ταιριάξει το πρώτο μέρος, στη γραμμή 1, και στη συνέχεια μένει αληθές μέχρι και όταν ταιριάξει το δεύτερο μέρος, στη γραμμή 3. Στη συνέχεια μένει ψευδές μέχρι το πρώτο μέρος να ταιριάξει πάλι στη γραμμή 5. Η εντολή sed χρησιμοποιείται για να τυπώσει τις πρώτες 7 γραμμές, ώστε η yes να μην τρέχει για πάντα. Αν είναι διαθέσιμη η εντολή head, είναι ισοδύναμη με την head -n7.
Όταν το πρώτο μέρος ενός προτύπου εύρους είναι πάντα αληθές, π.χ. 1, μπορεί να χρησιμοποιηθεί για να αρχίσει το εύρος στην αρχή της εισόδου. Όμοια, αν το δεύτερο μέρος είναι πάντα ψευδές, π.χ. 0, το εύρος εξακολουθεί μέχρι το τέλος της εισόδου, τότε η:
/^--κόψτε εδώ--$/, 0
τυπώνει γραμμές της εισόδου από την πρώτη γραμμή που ταιριάζει με την κανονική έκφραση ^--κόψτε εδώ--$ (μια γραμμή που περιέχει μόνο τη φράση "--κόψτε εδώ--"), μέχρι το τέλος.

Υπολογισμός συχνοτήτων λέξεων

Ο υπολογισμός των συχνοτήτων λέξεων χρησιμοποιεί συσχετιστικούς πίνακες (associative arrays):
BEGIN {
    FS="[^a-zA-Z]+"
}
{
     for (i=1; i<=NF; i++)
          words[tolower($i)]++
}
END {
    for (i in words)
         print i, words[i]
}
Η ενότητα BEGIN θέτει το διαχωριστικό πεδίων να είναι κάθε ακολουθία από μη-αλφαβητικούς χαρακτήρες (τα διαχωριστικά μπορούν να είναι κανονικές εκφράσεις). Ακολουθεί μια ενέργεια που επαναλαμβάνεται σε κάθε γραμμή της εισόδου. Σε αυτήν την περίπτωση, για κάθε πεδίο της γραμμής, προστίθεται 1 στον αριθμό των φορών που αυτή η λέξη, που έχει μετατραπεί σε μικρά γράμματα, εμφανίζεται. Τέλος, στην ενότητα END, τυπώνονται οι λέξεις με τις συχνότητές τους. Η γραμμή
for (i in words)
δημιουργεί ένα βρόχο επανάληψης πάνω στις λέξεις του πίνακα, που θέτει το i ίσο με κάθε δείκτη του πίνακα. Αυτό διαφέρει σε σχέση με τις περισσότερες άλλες γλώσσες, στις οποίες ένας τέτοιος βρόχος θα επαναλαμβανόταν για κάθε τιμή του πίνακα. Ο βρόχος δηλαδή εμφανίζει κάθε λέξη μαζί με τη συχνότητα που αυτή εμφανίστηκε.

Ταίριασμα προτύπου από τη γραμμή εντολών

Το πρόγραμμα μπορεί να αναπαρασταθεί με πολλούς τρόπους. Ο πρώτος χρησιμοποιεί το κέλυφος Bourne για να δημιουργήσει ένα σενάριο κελύφους που κάνει τα πάντα και είναι ο συντομότερος τρόπος:
$ cat grepinawk
pattern=$1
shift
awk '/'$pattern'/ { print FILENAME ":" $0 }' $*
$
Το πρότυπο $pattern στην εντολή της awk δεν προστατεύεται από εισαγωγικά. Ένα πρότυπο από μόνο του ελέγχει αν ολόκληρη η γραμμή ($0) ταιριάζει. Η FILENAME περιέχει το τρέχον όνομα αρχείου. Όπως έχει προαναφέρθηκε, η awk δεν έχει τελεστή συνένωσης αλλά η απλή παράθεση δύο συμβολοσειρών αρκεί για τη συνένωσή τους. Το $0 επεκτείνεται στην αρχική γραμμή εισόδου.
Υπάρχουν διάφοροι τρόποι να γραφεί το παράδειγμα. Το παρακάτω σενάριο κελύφους έχει πρόσβαση στο περιβάλλον απευθείας μέσα από την awk:
$ cat grepinawk
pattern=$1
shift
awk '$0 ~ ENVIRON["pattern"] { print FILENAME ":" $0 }' $*
$
Το σενάριο αυτό χρησιμοποιεί την ENVIRON, που είναι πίνακας που αναπαριστά το περιβάλλον: οι δείκτες είναι ονόματα μεταβλητών και τα αποτελέσματα είναι τιμές μεταβλητών, παρόμοια σε λειτουργικότητα με τη συνάρτηση getenv διάφορων πρότυπων βιβλιοθηκών και του POSIX. Το σενάριο δημιουργεί μια μεταβλητή περιβάλλοντος pattern που περιέχει την πρώτη παράμετρο και στη συνέχεια τη διαγράφει και δίνει εντολή στην awk να αναζητήσει το πρότυπο pattern σε κάθε αρχείο.
Ο τελεστής ~ ελέγχει αν ο αριστερός του τελεστέος ταιριάζει με το δεξιό, ενώ ο αντίστροφος τελεστής είναι ο !~. Μια κανονική έκφραση είναι απλά συμβολοσειρά και μπορεί να αποθηκευτεί σε μια μεταβλητή.
Ο επόμενος τρόπος χρησιμοποιεί ανάθεση μεταβλητών της γραμμής εντολών, όπου μια παράμετρος στην awk μπορεί να θεωρηθεί σαν ανάθεση σε μια μεταβλητή:
$ cat grepinawk
pattern=$1
shift
awk '$0 ~ pattern { print FILENAME ":" $0 }' "pattern=$pattern" $*
$
Τέλος, ο ακόλουθος κώδικας είναι γραμμένος αμιγώς σε awk, χωρίς βοήθεια από κέλυφος ή γνώσεις για την υλοποίηση του σεναρίου awk (όπως κάνει η ανάθεση μεταβλητών της γραμμής εντολών παραπάνω), αλλά είναι μεγαλύτερος:
BEGIN {
    pattern = ARGV[1]
    for (i = 1; i < ARGC; i++) # διαγράφει την 1η παράμετρο
        ARGV[i] = ARGV[i + 1]
    ARGC—if (ARGC == 1) { # μόνο το πρότυπο υπήρχε, άρα διάβασε από την είσοδο
        ARGC = 2
        ARGV[1] = "-"
    }
}
$0 ~ pattern { print FILENAME ":" $0 }
Το BEGIN χρειάζεται όχι μόνο για να εξάγει την πρώτη παράμετρο, αλλά και για να την εμποδίσει από το να ερμηνευτεί σαν όνομα αρχείου όταν τελειώσει η ενότητα BEGIN. Η ARGC, που είναι ο αριθμός των παραμέτρων, είναι πάντα ≥1, επειδή η ARGV[0] είναι το όνομα της εντολής που εκτέλεσε το σενάριο, συνήθως η συμβολοσειρά "awk". Επίσης η ARGV[ARGC] είναι η κενή συμβολοσειρά, "". Ο χαρακτήρας # αρχίζει ένα σχόλιο μέχρι το τέλος της γραμμής.
Στην ενότητα if η awk ελέγχει αν πρέπει να διαβάσει από την είσοδο πριν εκτελέσει την εντολή. Αυτό σημαίνει ότι η εντολή
awk 'prog'
λειτουργεί μόνο επειδή το ότι δεν υπάρχουν ονόματα αρχείων ελέγχεται πριν εκτελεστεί το prog! Αν η ARGC τεθεί ίση με 1 ώστε να μην υπάρχουν παράμετροι, η awk απλά θα τερματίσει γιατί δε θα βλέπει άλλα αρχεία εισόδου. Είναι επομένως ανάγκη το διάβασμα από την είσοδο με το ειδικό όνομα αρχείου -.

Αυτόνομα σενάρια AWK

Όπως και σε άλλες γλώσσες προγραμματισμού, είναι δυνατή η δημιουργία αυτόνομων σεναρίων σε AWK με τη χρήση της λεγόμενης σύνταξης "shebang", που τοποθετεί μια σήμανση στην αρχή του αρχείου για το πρόγραμμα με το οποίο εκτελούνται.
Για παράδειγμα, μπορεί να κατασκευαστεί μια εντολή του UNIX με όνομα hello.awk που να τυπώνει τη συμβολοσειρά "Γεια σου, κόσμε!", με τη δημιουργία ενός αρχείου hello.awk που να περιέχει τις εξής γραμμές:
#!/usr/bin/awk -f
BEGIN { print "Γεια σου, κόσμε!" }
Η επιλογή -f πληροφορεί την awk ότι η παράμετρος που ακολουθεί είναι το αρχείο από το οποίο θα διαβαστεί το πρόγραμμα awk και συμπληρώνεται από το κέλυφος τη στιγμή της εκτέλεσης.