How to use multiple arguments for awk with a shebang (i.e. #!)?
The shebang line has never been specified as part of POSIX, SUS, LSB or any other specification. AFAIK, it hasn't even been properly documented.
There is a rough consensus about what it does: take everything between the !
and the \n
and exec
it. The assumption is that everything between the !
and the \n
is a full absolute path to the interpreter. There is no consensus about what happens if it contains whitespace.
- Some operating systems simply treat the entire thing as the path. After all, in most operating systems, whitespace or dashes are legal in a path.
- Some operating systems split at whitespace and treat the first part as the path to the interpreter and the rest as individual arguments.
- Some operating systems split at the first whitespace and treat the front part as the path to the interpeter and the rest as a single argument (which is what you are seeing).
- Some even don't support shebang lines at all.
Thankfully, 1. and 4. seem to have died out, but 3. is pretty widespread, so you simply cannot rely on being able to pass more than one argument.
And since the location of commands is also not specified in POSIX or SUS, you generally use up that single argument by passing the executable's name to env
so that it can determine the executable's location; e.g.:
#!/usr/bin/env gawk
[Obviously, this still assumes a particular path for env
, but there are only very few systems where it lives in /bin
, so this is generally safe. The location of env
is a lot more standardized than the location of gawk
or even worse something like python
or ruby
or spidermonkey
.]
Which means that you cannot actually use any arguments at all.
This seems to work for me with (g)awk.
#!/bin/sh
arbitrary_long_name==0 "exec" "/usr/bin/gawk" "--re-interval" "-f" "$0" "$@"
# The real awk program starts here
{ print $0 }
Note the #!
runs /bin/sh
, so this script is first interpreted as a shell script.
At first, I simply tried "exec" "/usr/bin/gawk" "--re-interval" "-f" "$0" "$@"
, but awk treated that as a command and printed out every line of input unconditionally. That is why I put in the arbitrary_long_name==0
- it's supposed to fail all the time. You could replace it with some gibberish string. Basically, I was looking for a false-condition in awk that would not adversely affect the shell script.
In the shell script, the arbitrary_long_name==0
defines a variable called arbitrary_long_name
and sets it equal to =0
.
Although not exactly portable, starting with coreutils 8.30 and according to its documentation you will be able to use:
#!/usr/bin/env -S command arg1 arg2 ...
So given:
$ cat test.sh
#!/usr/bin/env -S showargs here 'is another' long arg -e "this and that " too
you will get:
% ./test.sh
$0 is '/usr/local/bin/showargs'
$1 is 'here'
$2 is 'is another'
$3 is 'long'
$4 is 'arg'
$5 is '-e'
$6 is 'this and that '
$7 is 'too'
$8 is './test.sh'
and in case you are curious showargs
is:
#!/usr/bin/env sh
echo "\$0 is '$0'"
i=1
for arg in "$@"; do
echo "\$$i is '$arg'"
i=$((i+1))
done
Original answer here.