Prig: the snobbish AWK

Prig is for Processing Records In Go. It’s like AWK, but snobbish (Go! static typing!). It’s also faster to execute, and if you know Go, you don’t need to learn AWK.

You can also read my article on why and how I wrote Prig. The tl;dr is “no good reason, I’m a geek!” 🙂

How to use Prig

To install prig, make sure Go is installed and then type go install[email protected]. Prig itself runs the generated code using go build, so even once you have a prig executable it requires the Go compiler to be installed.

As a simple example, you can try the following script. It prints a modified version of the second field of each line of input (the full URL in this example):

$ cat logs.txt
GET /robots.txt HTTP/1.1
GET /wp-admin/ HTTP/1.0

$ prig 'Println("" + S(2))' <logs.txt

To get help, run prig with a -h or --help argument. Help output is copied below, showing more examples at the bottom:

Prig v1.0.0 - Copyright (c) 2022 Ben Hoyt

Usage: prig [options] [-b 'begin code'] 'per-record code' [-e 'end code']

Prig is for Processing Records In Go. It's like AWK, but snobbish (Go! static
typing!). It runs 'begin code' first, then runs 'per-record code' for every
record (line) in the input, then runs 'end code'. Prig uses "go build", so it
requires the Go compiler:

  -F char | re     field separator (single character or multi-char regex)
  -g executable    Go compiler to use (eg: "go1.18rc1", default "go")
  -h, --help       print help message and exit
  -i import        import Go package (normally automatic)
  -s               print formatted Go source instead of running
  -V, --version    print version number and exit

Built-in functions:
  F(i int) float64 // return field i as float64, int, or string
  I(i int) int     // (i==0 is entire record, i==1 is first field)
  S(i int) string

  NF() int // return number of fields in current record
  NR() int // return number of current record

  Print(args ...interface{})                 // fmt.Print, but buffered
  Printf(format string, args ...interface{}) // fmt.Printf, but buffered
  Println(args ...interface{})               // fmt.Println, but buffered

  Match(re, s string) bool            // report whether s contains match of re
  Replace(re, s, repl string) string  // replace all re matches in s with repl
  Submatches(re, s string) []string   // return slice of submatches of re in s
  Substr(s string, n[, m] int) string // s[n:m] but safe and allow negative n/m

  Sort[T int|float64|string](s []T) []T
    // return new sorted slice; also Sort(s, Reverse) to sort descending
  SortMap[T int|float64|string](m map[string]T) []KV[T]
    // return sorted slice of key-value pairs
    // also Sort(s[, Reverse][, ByValue]) to sort descending or by value

  # Run an arbitrary Go snippet; don't process input
  prig -b 'Println("Hello, world!", math.Pi)'

  # Print the average value of the last field
  prig -b 's := 0.0' 's += F(NF())' -e 'Println(s / float64(NR()))'

  # Print 3rd field in milliseconds if line contains "GET" or "HEAD"
  prig 'if Match(`GET|HEAD`, S(0)) { Printf("%.0fms\n", F(3)*1000) }'

  # Print frequencies of unique words, most frequent first
  prig -b 'freqs := map[string]int{}' \
       'for i := 1; i <= NF(); i++ { freqs[strings.ToLower(S(i))]++ }' \
       -e 'for _, f := range SortMap(freqs, ByValue, Reverse) { ' \
       -e 'Println(f.K, f.V) }'

Prig uses the package, so imports are usually automatic (use -i if you need to disambiguate, for example between text/template and html/template). And that’s really all you need to know — the code snippets are pure Go.

Other information

If you’re looking for a real, POSIX-compatible version of AWK for use in Go programs, see my GoAWK project.

Prig was based on rp, a similar idea for Nim written by c-blake.

Prig is licensed under an open source MIT license.


View Github