I have a shell script in which I want to add a shebang. Given a variable defined as follows:
SHEBANG="#!/bin/sh"
My question is if I can use that variable in another script like this:
$SHEBANG
# other stuff...
I have a shell script in which I want to add a shebang. Given a variable defined as follows:
SHEBANG="#!/bin/sh"
My question is if I can use that variable in another script like this:
$SHEBANG
# other stuff...
No. The shebang line is processed by your kernel or system library* and doesn't process shell variables.
If you wanted to use an environment variable as the interpreter for some reason, you could create a "trampoline" executable (not script!) that re-spawned $SHEBANG "$@"
. You'd need to omit the #!
from the variable in this case; #!
is only useful when it is literally the first two bytes of a file.
Another option is to make use of the fact that scripts without shebang lines will (generally) be executed as shell scripts with either sh
or the calling shell. If there's a comment syntax for your shebang target that overlaps usefully with shell syntax, you could make it work. This strategy is or used to be common with Lisp interpreters. In the case of your example, where the interpreter is also sh
, this isn't viable.
You can push things even further on that line if you can rely on bash
being present: a script whose first line is
exec /bin/bash -c 'exec "$SHEBANG" <(tail -n +2 "$0")' "$0" "$@"
will work for most interpreters; Bash is used as an intermediate step so that process substitution can be used to have tail
skip the first line for us, so that we don't end up in an infinite loop. Some interpreters require that the file be seekable, and they won't accept a pipeline like this.
It's worth considering whether this is really something you want to do. Aside from any security concerns, it's just not very useful: few scripts can have their interpreters changed out like that, and where they can you'd almost certainly be better off with a more restricted piece of logic that spawned the correct one for the situation (perhaps a shell script in between, that calls the interpreter with the path to the real script as an argument).
* Not always; in certain circumstances some shells will read it themselves, but they still won't do variable expansion.
#!
line. So if execve
fails with ENOEXEC
because of a missing #!
line the shell may have other ways of deciding which interpreter to use. Those shouldn't be relied upon however, since different shells may choose different interpreters for the same script.
– kasperd
Nov 25 '17 at 12:07
I don't know if this is the use-case you have in mind, but one thing you can do is
#!/usr/bin/env python
so your script uses the first python
in $PATH
instead of needing to hard-code /usr/bin/python
, which might not work well on some system where the "good" version of python is in /usr/local/bin
or /opt
or whatever.
Why is it better to use "#!/usr/bin/env NAME" instead of "#!/path/to/NAME" as my shebang?
This doesn't create an infinite loop because the Python interpreter (which env
invokes on the file) just treats the #!
line as a comment. This is kind of the point of the #!
design, because many languages use #
as a comment character.
To extend this, you could write a polyglot program that initially runs under #!/bin/sh
, but then figures out what interpreter to actually use, and invokes that on the same script.
It turns out there's already a web page with multiline shebang tricks for many languages, including for compiled languages (the script feeds part of itself to a compiler and runs the output).
The perlrun
man page gives this example as an alternative to #!/usr/bin/env perl
:
#!/bin/sh
#! -*-perl-*-
eval 'exec perl -x -wS $0 ${1+"$@"}'
if 0;
your perl script goes here
When run as a /bin/sh
script, eval
runs perl -x -wS
on your script, passing along your args.
When Perl runs it, the eval
is controlled by the if 0;
on the next line, so it never runs. Unlike sh
, Perl statements are not ended by a newline.
You could make this more complex, using "$SHEBANG"
instead of perl
, or maybe even running multiple sh
commands to select a perl interpreter to use.
I think this is what you want:
xb@dnxb:/tmp$ cat /tmp/hole.sh
#!/usr/bin/$shell_var
echo 1
readlink "/proc/$$/exe"
echo 2
function f { echo hello world; }
echo 3
xb@dnxb:/tmp$ chmod +x /tmp/hole.sh
xb@dnxb:/tmp$ sudo vi /usr/bin/\$shell_var
xb@dnxb:/tmp$ cat /usr/bin/\$shell_var
#!/bin/bash
/bin/dash -- "$@"
xb@dnxb:/tmp$ sudo chmod +x /usr/bin/\$shell_var
xb@dnxb:/tmp$ ./hole.sh
1
/bin/dash
2
./hole.sh: 5: ./hole.sh: function: not found
3
xb@dnxb:/tmp$ sudo vi /usr/bin/\$shell_var
xb@dnxb:/tmp$ cat /usr/bin/\$shell_var
#!/bin/bash
/bin/bash -- "$@"
xb@dnxb:/tmp$ ./hole.sh
1
/bin/bash
2
3
xb@dnxb:/tmp$
Explanation:
Use \$shell_var
file act as a "variable" to defined the shell you want.
Since dash does not support function f { echo hello world; }
syntax, so it produced error function: not found
, but change shell to bash in \$shell_var
file will no more error.
The full file path (or relative path if ./hole.sh run in /tmp) will pass as $@
to the \$shell_var
file, and /tmp/hole.sh
will execute inside \$shell_var
.
export SHEBANG='#!/bin/tcsh'
and it fails with message#!/bin/tcsh: No such file or directory
despite the /bin/tcsh exists. – Jaroslav Kucera Nov 24 '17 at 20:35$SHEBANG
on a line by itself later in a bash or tcsh script, or if you'd run it interactively./bin/tcsh
is a valid path, but#!/bin/tcsh
isn't, unless you happen to be CDed to a directory where./#!
is a directory or symlink that exists. – Peter Cordes Nov 25 '17 at 02:36