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.
exec sed '
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.
exec sed -f - -- "$@" <<'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.
exec sed "$(cat <<'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.
f true; then exec sed -f "$0" "$@"; fi
# sed script starts here
- 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.)