2008-12-09 5 views
6

Questions:/usr/bin/env questions concernant la ligne de particularités tralala

  • Qu'est-ce que le noyau ne si vous collez un script shell dans la ligne de tralala?
  • Comment le noyau sait-il quel interpréteur lancer?

Explication:

Je voulais récemment d'écrire un wrapper autour /usr/bin/env parce que mon environnement CGI ne me permet pas de définir la variable PATH, sauf dans le monde (qui bien sûr ça craint!).

J'ai donc pensé: "OK, définissons PREPENDPATH et définissons PATH dans un wrapper autour d'env.". Le script résultant (appelé ici ENV.1) ressemblait à ceci:

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

qui ressemble à ça devrait fonctionner. J'ai vérifié comment ils réagissent tous deux, après la mise en 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!". Je suppose qu'il me manque quelque chose de très fondamental à propos de UNIX.

Je suis assez perdu, même après avoir regardé le code source de l'original env. Il définit l'environnement et lance le programme (ou alors il me semble ...).

Répondre

6

Tout d'abord, vous devriez très rarement utiliser $* et vous devriez presque toujours utiliser "[email protected]" à la place. Il y a un certain nombre de questions ici sur SO qui expliquent les tenants et les aboutissants de pourquoi.

Deuxième - la commande env a deux utilisations principales. L'un est d'imprimer l'environnement actuel; l'autre est de contrôler complètement l'environnement d'une commande quand elle est exécutée. Le troisième usage, que vous démontrez, est de modifier l'environnement, mais franchement, il n'y a pas besoin de cela - les obus 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 fonctionne command avec précision l'environnement défini par les options ENVVAR = valeur.

Le troisième mode - modifier l'environnement - est moins important car vous pouvez le faire avec des coques régulières (civilisées). (. Cela signifie « pas shell C » - encore une fois, il y a d'autres questions sur SO avec des réponses qui expliquent que) Par exemple, vous pouvez parfaitement faire:

#!/bin/bash 
export PATH=${PREPENDPATH:?}:$PATH 
exec python "[email protected]" 

Cette insiste sur le fait que $PREPENDPATH est réglé sur un non chaîne vide dans l'environnement, puis l'ajoute à $PATH et exporte le nouveau paramètre PATH. Puis, en utilisant ce nouveau PATH, il exécute le programme python avec les arguments pertinents. Le exec remplace le script shell par python. Notez que ceci est assez différent de:

#!/bin/bash 
PATH=${PREPENDPATH:?}:$PATH exec python "[email protected]" 

Superficiellement, c'est pareil. Cependant, cela va exécuter le python trouvé sur le PATH préexistant, mais avec la nouvelle valeur de PATH dans l'environnement du processus. Ainsi, dans l'exemple, vous finiriez par exécuter Python à partir de /usr/bin et non de /home/pi/prepend/bin.

Dans votre situation, je n'utiliserais probablement pas env et j'utiliserais simplement une variante appropriée du script avec l'exportation explicite.

La commande env est inhabituelle car elle ne reconnaît pas le double tiret pour séparer les options du reste de la commande. C'est en partie parce qu'il ne prend pas beaucoup d'options, et en partie parce qu'il n'est pas clair si les options ENVVAR = valeur devraient venir avant ou après le double tiret.

J'ai actuellement une série de scripts pour exécuter (différentes versions de) un serveur de base de données. Ces scripts utilisent vraiment env (et un tas de programmes cultivés à domicile) 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 "[email protected]" 

case "$*" in 
(*v*) track-oninit-v $stdout;; 
esac 
+0

Cette information est utile, mais comme le dit OP, son/son noyau ne fonctionnera pas non les binaires (tels que les bouchons de bash que vous fournissez) comme premier élément d'une ligne shebang. La mienne non plus; plus précisément, le noyau ne parvient pas à exécuter le script avec la ligne shebang problématique dans l'interpréteur (non-binaire) souhaité, et mon shell va essayer d'exécuter le script à la place. (Ma compréhension est que c'est un comportement de coquille ancienne que beaucoup de coquilles retiennent.) – dubiousjim

+0

Je ne comprends pas ce que vous dites? Voulez-vous dire qu'il y a (encore) des shells ou des noyaux qui ne gèreront pas #!/Bin/bash ou '#!/Bin/ksh' comme première ligne d'un script? Je ne prétends pas ou ne montre pas que vous pouvez utiliser '#!/Some/script' comme un shebang. –

+0

Ne revendiquant pas le premier. La question de l'OP est à propos de la seconde, cependant: il/elle a demandé "Qu'est-ce que le noyau fait si vous collez un shell-script dans la ligne shebang?" puis signale les problèmes qui proviennent de l'utilisation d'env.1 comme une ligne shebang, où env.1 est un script. (En fait, il cherchait des discussions sur les limites des lignes de shebang qui m'ont amené ici.) J'ai trouvé que votre message avait des informations utiles, alors merci de l'avoir fait, je voulais juste le souligner, pour d'autres lecteurs qui viendront plus tard. que ce dont vous parlez n'aide pas à surmonter les limites auxquelles le PO s'est heurté.) – dubiousjim

4

Vous devriez lire attentivement l'article wikipedia sur shebang. Lorsque votre système voit le nombre magique correspondant au shebang, il fait un execve sur le chemin donné après le shebang et donne le script lui-même en tant qu'argument.

Votre script échoue parce que le fichier que vous donnez (/usr/bin/env.1) n'est pas un exécutable, mais lui-même commence par un tralala ....

Idéalement, vous pouvez le résoudre en utilisant ...env sur votre script avec cette ligne comme le tralala:

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

Il ne fonctionnera pas bien sur Linux car il traite « /usr/bin/env.1 python » comme un chemin (il ne arguments fendus)

La seule façon dont je vois est d'écrire votre env.1 en C

EDIT: semble que personne ne me belives ^^, donc je l'ai écrit un simple et env.1.c sale:

#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; 
}