awk trampolining

I love writing awk scripts, but the shebang line can get out of hand sometimes. The common wisdom is something like this:

#!/usr/bin/env -S awk -f

However, this env invocation has a few issues:

The other option ---

#!/usr/bin/awk -f

--- has its own issues, namely that awk might not be installed there, or that the one that is installed there isn't the one you want to use.

time to jump!

What we need to do is "trampoline" from a normal shebang, like /bin/sh, to our programming language. I don't know why it's called trampoline, just that it is. Every language has its own special ridiculous way of doing this, since you basically have to pun on comment characters, but here's how I do it with awk:

#!/bin/sh
{ trampoline= "exec" "awk" "-f" "$0" "$@"; } # -- awk --
# --- Awk begins here ---

Since awk and sh share the `#` character for commenting, it's fairly simple:

The first line is your basic /bin/sh shebang. This causes /bin/sh (which is a standard path mandated by POSIX) to begin executing this file.

`{ ... }` syntax is a pattern rule in awk, and command grouping in sh. This means that the command inside will execute on every line of input, which isn't perfect, but it is what it is.

Now for the command itself:

sh trampoline execution

In sh, the variable `trampoline` is assigned the empty string "" for the remainder of the command line, which here is `exec awk -f "$0" "$@"`. The quoting around the first three words is completely allowed, and actually useful for the last two words. If you couldn't guess, this tells sh to replace its own process with awk, running the current script and given the current arguments.

The great part is that you could, if so inclined, include any number of awk command-line flags or arguments you wished---if you wanted to make a script that, for example, used the calling convention `<script> <DATE>`, you could write:

#!/bin/sh
{ trampoline= "exec" "awk" "-f" "$0" "-vDATE=$1"; }

More interesting uses are left as exercises to the reader.

awk trampoline execution

Once the script is passed off to awk, it begins reading the file again. The first line is skipped over since it starts with `#`, then the trampoline line is processed. It runs on every line since there is no condition before the braces, but that's okay: it just sets the `trampoline` variable to the string "execawk-f$0$@" (string juxtaposition is concatenation in awk). If you want to use a variable called `trampoline`, you could of course change that name to anything you like.

# -- awk -- ???

This extra little comment tells Emacs it's an awk script, instead of sh like it'd auto-detect. With vim, you'd do something like

{ trampoline= "exec" "awk" "-f" "$0" "$@"; } # vim: ft=awk

... I think. It's been a while since I used vim.

another way

While writing this post, I started playing around with it and came up with an even shorter, and dare I say more elegant, trampoline:

#!/bin/sh
NR==0 &amp;&amp; exec awk -f "$0" "$@";
# Awk script goes here

When I was testing, this didn't work in zsh---basically you need a sh(1) that will interpret `NR==0` as "set NR to =0". This command always succeeds, so the command after the `&&` fires, executing the awk script. Then, awk tests if `NR` is equal to 0, which it never is --- meaning the rest of the line doesn't get executed and the rest of the file goes off without a hitch (provided it's written well).

I find script trampolines super fun and love discovering new ones. So if you find another way for an awk script (or hell, even your favorite language!), feel free to send me an email and let me know!

NIL NIL