/ usr / bin / env questions regarding shebang line features

Questions

  • What does the kernel do if you embed a shell script in a shebang line?
  • How does the kernel know which interpreter is starting?

Explanation

I recently wanted to write a wrapper around / usr / bin / env because my CGI environment does not allow me to set the PATH variable except globally (which, of course, sucks!).

So, I thought, “Good. Let set PREPENDPATH and set PATH in a wrapper around env.” The result of the script (called env.1 here) is as follows:

#!/bin/bash /usr/bin/env PATH=$PREPENDPATH:$PATH $* 

which looks like it should work. I checked how they react after installing PREPENDPATH:

 $ which /usr/bin/env python /usr/bin/env /usr/bin/python $ which /usr/bin/env.1 python /usr/bin/env /home/pi/prepend/bin/python 

Look absolutely perfect! So far, so good. But look what happens with "Hello World!"

 # Shebang is #!/usr/bin/env python $ test-env.py Hello World! # Shebang is #!/usr/bin/env.1 python $ test-env.1.py Warning: unknown mime-type for "Hello World!" -- using "application/*" Error: no such file "Hello World!" 

I think I am missing something quite fundamental on UNIX.

I'm pretty lost even looking at the source code of the original env. It installs the environment and launches the program (or so it seems to me ...).

+6
linux unix shebang environment apache
source share
2 answers

First of all, you should very rarely use $* , and you should almost always use " $@ " . There are a number of questions on SO that explain the reasons and the reasons why.

Second, the env command has two main uses. One is to print the current environment; the other is to fully control the team environment when it starts. The third use that you demonstrate is a change in environment, but frankly, there is no need for it - shells are quite capable of handling this for you.

Mode 1:

 env 

Mode 2:

 env -i HOME=$HOME PATH=$PREPENDPATH:$PATH ... command args 

This version cancels all inherited environment variables and runs command with the exact environment specified by the ENVVAR = value parameters.

The third mode - changing the environment - is less important, because you can do it normally using ordinary (civilized) shells. (This means that the “non-C shell” - again, there are other SO questions with answers that explain this.) For example, you may well be able to do this:

 #!/bin/bash export PATH=${PREPENDPATH:?}:$PATH exec python " $@ " 

This means that $PREPENDPATH set to a non-empty string in the environment, and then adds it to $PATH and exports the new PATH setting. Then, using this new PATH, it executes the python program with the corresponding arguments. exec replaces the shell script with python . Please note that this is very different from:

 #!/bin/bash PATH=${PREPENDPATH:?}:$PATH exec python " $@ " 

Superficially, this is the same. However, this will be done by python found on a pre-existing PATH, albeit with a new PATH value in the production environment. So, in this example, you end Python execution from /usr/bin , and not from /home/pi/prepend/bin .

In your situation, I would probably not use env and just use the appropriate script option with explicit export.

The env command is unusual because it does not recognize a double dash to separate parameters from the rest of the command. This is partly due to the fact that many options are not required, and partly due to the fact that it is unclear whether the parameter values ​​ENVVAR = before or after the double dash.

In fact, I have a series of scripts to run (different versions) of a database server. These scripts really use env (and a bunch of home programs) to manage the server environment:

 #!/bin/ksh # # @(#)$Id: boot.black_19.sh,v 1.3 2008/06/25 15:44:44 jleffler Exp $ # # Boot server black_19 - IDS 11.50.FC1 IXD=/usr/informix/11.50.FC1 IXS=black_19 cd $IXD || exit 1 IXF=$IXD/do.not.start.$IXS if [ -f $IXF ] then echo "$0: will not start server $IXS because file $IXF exists" 1>&2 exit 1 fi ONINIT=$IXD/bin/oninit.$IXS if [ ! -f $ONINIT ] then ONINIT=$IXD/bin/oninit fi tmpdir=$IXD/tmp DAEMONIZE=/work1/jleffler/bin/daemonize stdout=$tmpdir/$IXS.stdout stderr=$tmpdir/$IXS.stderr if [ ! -d $tmpdir ] then asroot -u informix -g informix -C -- mkdir -p $tmpdir fi # Specialized programs carried to extremes: # * asroot sets UID and GID values and then executes # * env, which sets the environment precisely and then executes # * daemonize, which makes the process into a daemon and then executes # * oninit, which is what we really wanted to run in the first place! # NB: daemonize defaults stdin to /dev/null and could set umask but # oninit dinks with it all the time so there is no real point. # NB: daemonize should not be necessary, but oninit doesn't close its # controlling terminal and therefore causes cron-jobs that restart # it to hang, and interactive shells that started it to hang, and # tracing programs. # ??? Anyone want to integrate truss into this sequence? asroot -u informix -g informix -C -a dbaao -a dbsso -- \ env -i HOME=$IXD \ INFORMIXDIR=$IXD \ INFORMIXSERVER=$IXS \ INFORMIXCONCSMCFG=$IXD/etc/concsm.$IXS \ IFX_LISTEN_TIMEOUT=3 \ ONCONFIG=onconfig.$IXS \ PATH=/usr/bin:$IXD/bin \ SHELL=/usr/bin/ksh \ TZ=UTC0 \ $DAEMONIZE -act -d $IXD -o $stdout -e $stderr -- \ $ONINIT " $@ " case "$*" in (*v*) track-oninit-v $stdout;; esac 
+6
source share

You should carefully read the wikipedia article about shebang .

When your system sees the magic number corresponding to shebang, it makes execve on this path after shebang and gives the script itself as an argument.

Your script fails because the file you give ( /usr/bin/env.1 ) is not an executable, but starts with shebang ....

Ideally, you can enable it with ... env on a script with this line as shebang:

 #!/usr/bin/env /usr/bin/env.1 python 

It will not work, although on linux, since it treats " /usr/bin/env.1 python " as a path (it does not separate the arguments)

So the only way I see is to write env.1 in C

EDIT: Nobody seems to believe me ^^, so I wrote a simple and dirty env.1.c :

 #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> const char* prependpath = "/your/prepend/path/here:"; int main(int argc, char** argv){ int args_len = argc + 1; char* args[args_len]; const char* env = "/usr/bin/env"; int i; /* arguments: the same */ args[0] = env; for(i=1; i<argc; i++) args[i] = argv[i]; args[argc] = NULL; /* environment */ char* p = getenv("PATH"); char* newpath = (char*) malloc(strlen(p) + strlen(prependpath)); sprintf(newpath, "%s%s", prependpath, p); setenv("PATH", newpath, 1); execv(env, args); return 0; } 
+4
source share

All Articles