/ usr / bin / env questions sur les particularités de la ligne shebang
-
20-08-2019 - |
Question
Questions :
- Que fait le noyau si vous collez un script shell dans la ligne shebang?
- Comment le noyau sait-il quel interpréteur lancer?
Explication :
Je voulais récemment écrire un wrapper autour de / usr / bin / env car mon environnement CGI ne me permettait pas de définir la variable PATH , sauf globalement (qui Bien sûr, c'est nul!).
Alors j'ai pensé, & "OK. Définissons PREPENDPATH et PATH dans une enveloppe autour de env. & ";". Le script résultant (appelé ici env.1 ) se présente comme suit:
#!/bin/bash
/usr/bin/env PATH=$PREPENDPATH:$PATH $*
qui ressemble à cela devrait fonctionner. J'ai vérifié comment ils réagissaient tous les deux, après avoir réglé 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
Regardez absolument parfait ! Jusqu'ici tout va bien. Mais regardez ce qui arrive à & "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!"
Je suppose qu'il me manque quelque chose d'assez fondamental sur UNIX.
Je suis assez perdu, même après avoir consulté le code source de env d'origine. Il définit l'environnement et lance le programme (ou du moins me semble-t-il ...).
La solution
Tout d’abord, vous devriez très rarement utiliser $*
et vous devriez presque toujours utiliser "$@"
à la place. Il y a un certain nombre de questions sur SO qui expliquent les tenants et les aboutissants de la raison.
Deuxièmement - la commande env
a deux utilisations principales. L'une consiste à imprimer l'environnement actuel; l'autre consiste à contrôler complètement l'environnement d'une commande lorsqu'elle est exécutée. La troisième utilisation, que vous démontrez, est de modifier l’environnement, mais franchement, cela n’est pas nécessaire - les coques sont tout à fait capables de gérer cela pour vous.
Mode 1:
env
Mode 2:
env -i HOME=$HOME PATH=$PREPENDPATH:$PATH ... command args
Cette version annule toutes les variables d'environnement héritées et exécute command
précisément l'environnement défini par les options ENVVAR = value.
Le troisième mode - modification de l'environnement - est moins important, car vous pouvez le faire très bien avec des obus ordinaires (civilisés). (Cela signifie & "Pas le shell C &"; - encore une fois, il y a d'autres questions sur SO qui donnent des réponses qui expliquent cela.) Par exemple, vous pourriez parfaitement faire:
#!/bin/bash
export PATH=${PREPENDPATH:?}:$PATH
exec python "$@"
Cela insiste sur le fait que $PREPENDPATH
est défini sur une chaîne non vide dans l'environnement, qu'il est ensuite ajouté avant $PATH
, puis qu'il exporte le nouveau paramètre PATH. Ensuite, en utilisant ce nouveau PATH, il exécute le programme python
avec les arguments appropriés. Le exec
remplace le script de shell par /usr/bin
. Notez que ceci est assez différent de:
#!/bin/bash
PATH=${PREPENDPATH:?}:$PATH exec python "$@"
Superficiellement, c'est la même chose. Cependant, cela exécutera le /home/pi/prepend/bin
trouvé sur le PATH préexistant, bien qu'avec la nouvelle valeur de PATH dans l'environnement du processus. Ainsi, dans l’exemple, vous exécuteriez Python à partir de <=> et non celui à partir de <=>.
Dans votre cas, je n'utiliserais probablement pas <=> et utiliserais simplement une variante appropriée du script avec l'exportation explicite.
La commande <=> est inhabituelle car elle ne reconnaît pas le double tiret pour séparer les options du reste de la commande. Ceci est dû en partie au fait qu’il ne prend pas beaucoup d’options et en partie au fait qu’il n’est pas clair si les options ENVVAR = valeur doivent précéder ou suivre le double tiret.
J'ai en fait une série de scripts pour exécuter (différentes versions de) un serveur de base de données. Ces scripts utilisent réellement <=> (et un ensemble de programmes développés en interne) pour contrôler l’environnement du serveur:
#!/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
Autres conseils
Vous devriez lire attentivement l'article de Wikipédia sur shebang .
Lorsque votre système voit le nombre magique correspondant au shebang, il effectue un execve
sur le chemin donné après le shebang et donne le script lui-même sous forme d'argument.
Votre script échoue car le fichier que vous fournissez (/usr/bin/env.1
) n'est pas un exécutable , mais commence par un shebang ....
Idéalement, vous pouvez résoudre ce problème en utilisant ... env
sur votre script avec cette ligne en tant que shebang:
#!/usr/bin/env /usr/bin/env.1 python
Cela ne fonctionnera pas sous Linux, car il traite & "; /usr/bin/env.1 python
&"; en tant que chemin (il ne divise pas les arguments)
Le seul moyen que je vois est donc d'écrire votre env.1
en C
EDIT: on dirait que personne ne me croit ^^, alors j’ai écrit un texte simple et sale 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;
}