You can't, portably, put more than one argument on a #!
line. That means only a full path and one argument (e.g. #!/bin/sed -f
or #!/usr/bin/sed -f
), or #!/usr/bin/env
and no argument to the interpreter.
A workaround to get a portable script is to use #!/bin/sh
and a shell wrapper, passing the sed script as a command-line argument. Note that this is not sanctioned by POSIX (multi-instruction scripts must be written with a separate -e
argument for each instruction for portability), but it works with many implementations.
#!/bin/sh
exec sed '
s/a/b/
' "$@"
For a long script, it may be more convenient to use a heredoc. An advantage of a heredoc is that you don't need to quote the single quotes inside, if any. A major downside is that the script is fed to sed on its standard input, with two annoying consequences. Some versions of sed require -f /dev/stdin
instead of -f -
, which is a problem for portability. Worse, the script can't act as a filter, because the standard input is the script and can't be the data.
#!/bin/sh
exec sed -f - -- "$@" <<'EOF'
s/a/b/
EOF
The downside of the heredoc can be remedied by a useful use of cat
. Since this puts the whole script on the command line again, it's not POSIX-compliant, but largely portable in practice.
#!/bin/sh
exec sed "$(cat <<'EOF')" -- "$@"
s/a/b/
EOF
Another workaround is to write a script that can be parsed both by sh and by sed. This is portable, reasonably efficient, just a little ugly.
#! /bin/sh
b ()
{
x
}
i\
f true; then exec sed -f "$0" "$@"; fi
: ()
# sed script starts here
s/a/b/
Explanations:
- Under sh: define a function called
b
; the contents don't matter as long as the function is syntactically well-formed (in particular, you can't have an empty function). Then if true (i.e. always), execute sed
on the script.
- Under sed: branch to the
()
label, then some well-formed input. Then an i
command, which has no effect because it's always skipped. Finally the ()
label followed by the useful part of the script.
- Tested under GNU sed, BusyBox and OpenBSD. (You can get away with something simpler on GNU sed, but OpenBSD sed is picky about the parts it skips.)
Best Answer
No,
env
is not guaranteed to be in/usr/bin
, as you can read in the history of the shebang mechanism, in the section "The env utility":It does not move the problem completely, because of the flexibility of
env
searching$PATH
. If you happen to get some scripts that use a different location forenv
from yours, you only have to know about where yourenv
lives, and not also whereperl
,python
, and other interpreters might be installed.And you don't have to hard code
/opt/python/3.3.2/bin/python3.3
if that is the firstpython3.3
executable in your PATH. You can just rely onenv
finding it, so you don't have to update each and every script if you upgrade to use /opt/python/3.3.3/bin/python3.3. You script header stays the same: