44

I have an awk script that I have defined thus:

#!/usr/bin/env awk BEGIN { if (!len) len = 1; end = start + len } { for (i = start; i < end; i++) { print $1 } } 

I have saved it as columns and chmod +x'd it. I want invoke it so that start and end are defined as it traverses over a file. I was thinking this should work:

cat some_file | columns -v start=2 

But it doesn't. Help!

1

5 Answers 5

69

Try using:

#!/usr/bin/awk -f 

as an interpreter

Sign up to request clarification or add additional context in comments.

6 Comments

+1 for the most portable solution, as it only uses a single argument. Even though the path to awk is hard-coded - of necessity, due to having to make do with one argument - this should work on all modern Unix platforms. (Do tell if you know of any where it doesn't.)
@mklement0 It doesn't on mine (NixOS). Since NixOS keeps all packages isolated and on non standard paths. I think using mss's answer would be better in my case. EDIT That doesn't work either since -S is non POSIX compliant :(
@PallavAgarwal: Good to know about how NixOS differs. Note that as long your OS can handle multiple tokens on the shebang line, you're good; in other words: #!/usr/bin/env awk -f may work for you (works on OS X, for instance, but not on Linux).
@mklement0 Had that worked, I would've never come across this question in the first place. And anyway the whole point of trying to find the right shebang was to make a script that works everywhere. If I had to, I could write #!/nix/store/<full path to awk executable> -f
@PallavAgarwal: Understood. We can conclude that there is no portable solution that works on all platforms (short of creating a wrapper executable).
|
17

env is the easiest way to handle this problem:

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

to add more options, and to ensure no interference with your arguments, and awk's arguments:

#!/usr/bin/env -S awk -F: -f ${_} -- BEGIN { # delete argv[1], which == ENVIRON[_] delete ARGV[1] } # rest of my awk program 

as env has a POSIX standard, this shbang should get you around the difficulties of non-standard shbang implementations across unixen.

EDIT

after having written this I realized that '-S' is a non-POSIX compliant FreeBSD env extension. So shell wrapper is probably the way to go, unfortunate as that is.

2 Comments

We're looking to add this to GNU coreutils after version 8.29
Note that ${_} relies on the _ environment variable being set by the shell from which you launched the script, and will not work if the script isn’t launched directly from a shell.
6

Unfortunately, this is not easy to solve in a portable way. The standard technique looks like this (substitute /usr/bin/awk for your awk path):

#!/usr/bin/awk -f BEGIN { if (!len) len = 1; end = start + len } { for (i = start; i < end; i++) { print $1 } } 

The hard-coded awk path and non-standard -f flag, makes this not portable across all *nixes. If you are only ever going to run your script on one machine, then this may work fine. However, to make a portable awk script, you will need to wrap it in a shell script. Here are two ways that you can do it in one file:

The first way is standard and easy to read:

#!/bin/sh awk ' BEGIN { if (!len) len = 1; end = start + len } { for (i = start; i < end; i++) { print $1 } } ' "$@" 

Unfortunately, this falls short in two key ways:

  1. If your awk script contains a ' character, you will need to type it like this: '"'"' to "escape" it.
  2. If you are using a text editor with syntax highlighting, you will not get your awk script properly highlighted.

Another solution, is to use sed to strip out the sh wrapper:

#!/bin/sh exec awk "$(sed '1,2d' "$0")" "$@" BEGIN { if (!len) len = 1; end = start + len } { for (i = start; i < end; i++) { print $1 } } 

This is something like a two line shabang header. It calls awk using the file from line 3 down as the script argument. This allows you to keep your pretty syntax highlighting and you can still use ' characters to your heart's content. The two downsides I see are:

  1. Arguably this is non-intuitive to read.
  2. If you are using a code linter, it may not like this.

Comments

0

Below is the answer for this problem -

#!/bin/awk -f 

3 Comments

While this work well for gawk, it has problems on debian/ubuntu as they use mawk on /usr/bin/awk that does not support -f option.
@OliverGondža in my system (Ubuntu 20.04) it works with mawk, too.
On Solaris this would call old, broken awk, see awk.freeshell.org/oawk. Don't do it.
0

Alternatively the awk script could be placed in a shell script with just the awk command inside:

#!/bin/sh awk 'BEGIN { if (!len) len = 1; end = start + len } { for (i = start; i < end; i++) { print $1 } }' <file> 

This avoids using awk in the shebang and provides the possibility to execute the awk script either as a simple shell script with

./script 

or sourcing it from the current shell (or a shell script) with

. script 

The latter option will avoid spawing a new shell and just executing the awk command.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.