The way to use `/usr/bin/env sed -f ` in shebang

envexecutablescriptingsed

Typing /usr/bin/env sed -f in terminal works.

But if use it as a shebang,

#!/usr/bin/env sed -f 
s/a/b/

The script will be fail to execute:

/usr/bin/env: sed -f: No such file or directory

I kind of believe that it's related with the -f. But how to resolve this?

Best Answer

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.)