39

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!

mklement0
  • 382,024
  • 64
  • 607
  • 775
Ollie Saunders
  • 7,787
  • 3
  • 29
  • 37

4 Answers4

65

Try using:

#!/usr/bin/awk -f

as an interpreter

agsamek
  • 8,734
  • 11
  • 36
  • 43
  • +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 Oct 14 '14 at 18:31
  • 2
    @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 :( – Pallav Agarwal Jan 31 '16 at 10:23
  • @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 Jan 31 '16 at 14:44
  • 1
    @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/ -f – Pallav Agarwal Feb 01 '16 at 14:58
  • @PallavAgarwal: Understood. We can conclude that there is no portable solution that works on all platforms (short of creating a wrapper executable). – mklement0 Feb 01 '16 at 15:49
13

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.

Andreas Louv
  • 46,145
  • 13
  • 104
  • 123
mss
  • 139
  • 1
  • 2
  • 6
    We're looking to add this to GNU coreutils after version 8.29 – pixelbeat Apr 26 '18 at 05:07
  • Note that `${_}` relies on the `_` environment variable being [set by the shell](https://www.gnu.org/software/bash/manual/bash.html#index-_005f) from which you launched the script, and will not work if the script isn’t launched directly from a shell. – Anders Kaseorg Sep 09 '22 at 03:26
3

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.
Sudo Bash
  • 373
  • 5
  • 14
1

Below is the answer for this problem -

#!/bin/awk -f 
VIPIN KUMAR
  • 3,019
  • 1
  • 23
  • 34