Pourquoi est-il préférable d'utiliser "#! / Usr / bin / env NAME" au lieu de "#! / Chemin / to / NAME" comme mon shebang?

460

Je remarque que certains scripts que j'ai acquis auprès d'autres ont le shebang #!/path/to/NAMEalors que d'autres (utilisant le même outil, NAME) ont le shebang #!/usr/bin/env NAME.

Les deux semblent fonctionner correctement. Dans les tutoriels (sur Python, par exemple), il semble y avoir une suggestion que ce dernier shebang est meilleur. Mais, je ne comprends pas très bien pourquoi.

Je me rends compte que, pour utiliser ce dernier shebang, NAME doit être dans le chemin alors que le premier shebang n'a pas cette restriction.

De plus, il me semble que le premier serait le meilleur shebang, car il spécifie précisément l'emplacement de NAME. Ainsi, dans ce cas, s'il existe plusieurs versions de NAME (par exemple, / usr / bin / NAME, / usr / local / bin / NAME), le premier cas indique celle à utiliser.

Ma question est la suivante: pourquoi le premier shebang est-il préféré au second?

Le Geeko61
la source
12
Voir cette réponse ...
jasonwryan
@ TheGeeko61: Dans mon cas, quelque chose ne fonctionnait pas et certaines variables n'étaient pas dans env. Je suggère donc d'utiliser ce shebang pour vérifier si env est correctement chargé.
Gigamegs

Réponses:

463

Ce n'est pas forcément mieux.

L'avantage #!/usr/bin/env pythonest qu'il utilisera le pythonfichier exécutable qui apparaît en premier dans l'utilisateur $PATH.

L' inconvénient de #!/usr/bin/env pythonest qu'il utilisera le pythonfichier exécutable qui apparaît en premier dans l'utilisateur $PATH.

Cela signifie que le script peut se comporter différemment selon son exécution. Pour un utilisateur, il peut utiliser l’ /usr/bin/pythoninstallation du système d’exploitation. D'autre part, il pourrait utiliser un expérimental /home/phred/bin/pythonqui ne fonctionne pas correctement.

Et s’il pythonn’est installé que dans /usr/local/bin, un utilisateur qui n’a pas /usr/local/binin $PATHne pourra même pas exécuter le script. (Ce n'est probablement pas trop probable sur les systèmes modernes, mais cela pourrait facilement arriver à un interprète plus obscur.)

En spécifiant, #!/usr/bin/pythonvous spécifiez exactement quel interpréteur sera utilisé pour exécuter le script sur un système particulier .

Un autre problème potentiel est que l' #!/usr/bin/envastuce ne vous permet pas de transmettre des arguments à l'intrepreter (autre que le nom du script, qui est transmis de manière implicite). Ce n'est généralement pas un problème, mais ça peut l'être. De nombreux scripts Perl sont écrits avec #!/usr/bin/perl -w, mais use warnings;c'est le remplacement recommandé de nos jours. Les scripts Csh doivent utiliser #!/bin/csh -f- mais les scripts csh ne sont pas recommandés en premier lieu. Mais il pourrait y avoir d'autres exemples.

J'ai un certain nombre de scripts Perl dans un système de contrôle de source personnel que j'installe lorsque je crée un compte sur un nouveau système. J'utilise un script d'installation qui modifie la #!ligne de chaque script à mesure qu'il l'installe dans mon fichier $HOME/bin. (Je n'ai pas eu à utiliser autre chose que #!/usr/bin/perlrécemment; cela remonte à une époque où Perl n'était souvent pas installé par défaut.)

Un point mineur: le #!/usr/bin/envtruc est sans doute un abus de la envcommande, qui était à l'origine destiné (comme son nom l'indique) à appeler une commande avec un environnement modifié. En outre, certains systèmes plus anciens (y compris SunOS 4, si ma mémoire est bonne) ne disposaient pas de la envcommande /usr/bin. Ni l'un ni l'autre ne sont susceptibles d'être une préoccupation importante. envfonctionne de cette façon, beaucoup de scripts utilisent cette #!/usr/bin/envastuce et les fournisseurs de système d’exploitation ne feront probablement rien pour la résoudre. Cela peut poser problème si vous voulez que votre script soit exécuté sur un système très ancien, mais vous devrez probablement le modifier quand même.

Un autre problème possible (merci à Sopalajo de Arrierez de l'avoir signalé dans les commentaires) est que les tâches cron s'exécutent dans un environnement restreint. En particulier, $PATHest généralement quelque chose comme /usr/bin:/bin. Donc, si le répertoire contenant l'interpréteur ne se trouve pas dans l'un de ces répertoires, même s'il est dans votre répertoire $PATHutilisateur par défaut , alors l' /usr/bin/envastuce ne fonctionnera pas. Vous pouvez spécifier le chemin exact ou ajouter une ligne à votre crontab à définir $PATH( man 5 crontabpour plus de détails).

Keith Thompson
la source
4
Si / usr / bin / perl est associé à Perl 5.8, $ HOME / bin / perl est associé à 5.12 et à un script nécessitant 5.12 codes durs / usr / bin / perl dans les shebangs, l'exécution du script peut s'avérer très pénible. J'ai rarement vu que / usr / bin / env perl saisir perl depuis PATH posait problème, mais c'est souvent très utile. Et c'est beaucoup plus joli que l'exécutif!
William Pursell
8
Il est intéressant de noter que, si vous souhaitez utiliser une version d'interprète spécifique, / usr / bin / env est encore meilleur. tout simplement parce qu'il en général sont multiples versions d'interprétation installés sur votre machine nommée perl5, perl5.12, perl5.10, python3.3, python3.32, etc. et si votre application a été testé sur cette version spécifique, vous pouvez spécifier encore #! / usr / bin / env perl5.12 et ça ira même si l’utilisateur l’a installé quelque part d’inhabituel. D'après mon expérience, «python» n'est généralement qu'un lien symbolique vers la version standard du système (pas nécessairement la version la plus récente du système).
racine le
6
@ root: pour Perl, cela use v5.12;sert en partie à cela. Et #!/usr/bin/env perl5.12échouera si le système utilise Perl 5.14 mais pas 5.12. Pour Python 2 vs. 3, #!/usr/bin/python2et #!/usr/bin/python3sont susceptibles de fonctionner.
Keith Thompson
3
@ GoodPerson: Supposons que j'écrive un script Perl dans lequel être installé /usr/local/bin. Je sais que cela fonctionne correctement avec /usr/bin/perl. Je ne sais pas du tout si cela fonctionne avec le perlfichier exécutable qu'un utilisateur aléatoire a en lui $PATH. Peut-être que quelqu'un expérimente une version ancienne de Perl; parce que j'ai spécifié #!/usr/bin/perl, mon script (que l'utilisateur ne sait même pas nécessairement ou dont le souci est un script Perl) n'arrête pas de fonctionner.
Keith Thompson
5
Si cela ne fonctionne pas /usr/bin/perl, je le saurai très rapidement, et il incombe au propriétaire / administrateur du système de le tenir à jour. Si vous voulez exécuter mon script avec votre propre perl, n'hésitez pas à saisir et à modifier une copie ou à l'invoquer via perl foo. (Et vous pourriez envisager la possibilité que les 55 personnes qui ont voté pour cette réponse connaissent également une chose ou deux. Il est certainement possible que vous ayez raison et ils ont tous tort, mais ce n'est pas ainsi que je parierais.)
Keith Thompson
101

Parce que / usr / bin / env peut interpréter votre $PATH, ce qui rend les scripts plus portables.

#!/usr/local/bin/python

N'exécutera votre script que si python est installé dans / usr / local / bin.

#!/usr/bin/env python

Va interpréter votre $PATHet trouver python dans n’importe quel répertoire de votre $PATH.

Ainsi, votre script est plus portable et fonctionnera sans modification sur les systèmes où python est installé en tant que /usr/bin/python, ou /usr/local/bin/python, ou même les répertoires personnalisés (qui ont été ajoutés $PATH), comme /opt/local/bin/python.

La portabilité est la seule raison pour laquelle envon préfère utiliser des chemins codés en dur.

Tim Kennedy
la source
19
Les répertoires personnalisés pour les pythonexécutables sont particulièrement courants lorsque l' virtualenvutilisation augmente.
Xiong Chiamiov
1
Pourquoi pas #! pythonpourquoi?
Kristianp
6
#! pythonn'est pas utilisé car vous devez vous trouver dans le même répertoire que le binaire python, car le mot clé à nu pythonest interprété comme le chemin complet du fichier. Si vous n'avez pas de fichier binaire python dans le répertoire en cours, vous obtiendrez une erreur du type bash: ./script.py: python: bad interpreter: No such file or directory. C'est pareil que si tu utilisais#! /not/a/real/path/python
Tim Kennedy
47

La spécification du chemin absolu est plus précise sur un système donné. L'inconvénient est que c'est trop précis. Supposons que vous réalisiez que l'installation système de Perl est trop ancienne pour vos scripts et que vous souhaitez utiliser les vôtres: vous devez alors éditer les scripts et passer #!/usr/bin/perlà #!/home/myname/bin/perl. Pire, si vous avez Perl /usr/binsur certaines machines, /usr/local/binsur d’autres et /home/myname/bin/perlsur d’autres encore, vous devrez conserver trois copies distinctes des scripts et exécuter celle qui convient sur chaque machine.

#!/usr/bin/envpauses si PATHest mauvais, mais presque tout. Il PATHest très rarement utile d’ essayer de manipuler un fichier bad , ce qui indique que vous ne connaissez que très peu le système sur lequel le script est exécuté. Vous ne pouvez donc pas vous fier à un chemin absolu.

Il existe deux programmes dont vous pouvez vous fier à l'emplacement de presque toutes les variantes d'Unix: /bin/shet /usr/bin/env. Certaines variantes Unix, obscures et la plupart du temps retirées, avaient /bin/envsans avoir /usr/bin/env, mais il est peu probable que vous les rencontriez. Les systèmes modernes ont /usr/bin/envprécisément pour cause leur utilisation généralisée dans les shebangs. /usr/bin/envest quelque chose sur lequel vous pouvez compter.

En dehors de cela /bin/sh, le seul moment où vous devez utiliser un chemin absolu dans un shebang est lorsque votre script n'est pas conçu pour être portable, vous pouvez donc compter sur un emplacement connu pour l'interprète. Par exemple, un script bash ne fonctionnant que sous Linux peut être utilisé en toute sécurité #!/bin/bash. Un script destiné uniquement à être utilisé en interne peut s’appuyer sur les conventions de localisation des interprètes internes.

#!/usr/bin/enva des inconvénients. C'est plus flexible que de spécifier un chemin absolu, mais nécessite toujours de connaître le nom de l'interprète. Parfois, vous voudrez peut-être exécuter un interpréteur qui ne se trouve pas dans le $PATH, par exemple dans un emplacement relatif au script. Dans de tels cas, vous pouvez souvent créer un script polyglotte pouvant être interprété à la fois par le shell standard et par l'interprète de votre choix. Par exemple, pour rendre un script Python 2 portable à la fois vers des systèmes où pythonse trouvent Python 3 et python2Python 2, et vers des systèmes où pythonPython 2 python2n'existe pas:

#!/bin/sh
''':'
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0" "$@"
else
  exec python "$0" "$@"
fi
'''
# real Python script starts here
def …
Gilles
la source
22

En particulier pour Perl, utiliser #!/usr/bin/envest une mauvaise idée pour deux raisons.

Tout d'abord, ce n'est pas portable. Sur certaines plateformes obscures, env n’est pas dans / usr / bin. Deuxièmement, comme l' a noté Keith Thompson , le fait de passer des arguments sur la ligne shebang peut poser problème. La solution portable maximale est la suivante:

#!/bin/sh
exec perl -x "$0" "$@"
#!perl

Pour plus d'informations sur son fonctionnement, voir 'perldoc perlrun' et ce qu'il dit à propos de l'argument -x.

DrHyde
la source
Exactement la réponse que je cherchais: comment écrire un script "shebang" pour perl qui permette de passer des arguments supplémentaires à perl (la dernière ligne de votre exemple accepte des arguments supplémentaires).
AmokHuginnsson
15

La raison pour laquelle il existe une distinction entre les deux est due à la façon dont les scripts sont exécutés.

Utiliser /usr/bin/env(ce qui, comme indiqué dans d’autres réponses, n’appartient pas à /usr/bincertains systèmes d’exploitation) est obligatoire car vous ne pouvez pas simplement mettre un nom d’exécutable après le #!- cela doit être un chemin absolu. En effet, le #!mécanisme fonctionne à un niveau inférieur à celui du shell. Cela fait partie du chargeur binaire du noyau. Cela peut être testé. Mettez ceci dans un fichier et marquez-le comme exécutable:

#!bash

echo 'foo'

Vous constaterez qu'il affiche une erreur comme celle-ci lorsque vous essayez de l'exécuter:

Failed to execute process './test.sh'. Reason:
The file './test.sh' does not exist or could not be executed.

Si un fichier est marqué comme exécutable et commence par un #!, le noyau (qui ne connaît pas $PATHou le répertoire en cours: ce sont des concepts utilisateur-terrain) recherchera un fichier en utilisant un chemin absolu. Parce que l'utilisation d'un chemin absolu est problématique (comme mentionné dans d'autres réponses), quelqu'un a proposé une astuce: vous pouvez exécuter /usr/bin/env(ce qui est presque toujours à cet endroit) pour exécuter quelque chose avec $PATH.

Tiffany Bennett
la source
11

Il y a deux autres problèmes avec l'utilisation #!/usr/bin/env

  1. Cela ne résout pas le problème de spécifier le chemin complet de l'interprète, mais le déplace simplement env.

    envn’est pas plus garanti d’être /usr/bin/envqu’il bashn’est garanti d’être en /bin/bashpython /usr/bin/python.

  2. env écrase ARGV [0] avec le nom de l'interpréteur (par exemple, bash ou python).

    Cela évite que le nom de votre script apparaisse dans, par exemple, la pssortie (ou modifie comment / où il apparaît) et rend impossible sa recherche avec, par exemple,ps -C scriptname.sh

[mise à jour 2016-06-04]

Et un troisième problème:

  1. Changer votre PATH représente plus de travail que de simplement éditer la première ligne d'un script, en particulier lorsque le script de telles modifications est trivial. par exemple:

    printf "%s\n" 1 i '#!'$(type -P python2) . w | ed foo.py

    L'ajout ou la mise en attente préalable d'un répertoire dans $ PATH est assez facile (bien que vous deviez toujours éditer un fichier pour le rendre permanent - votre ~/.profileou autre chose - et il est loin d'être facile de script QU'éditer car le PATH peut être défini n'importe où dans le script. , pas dans la première ligne).

    Changer l'ordre des répertoires PATH est beaucoup plus difficile ... et bien plus difficile que de simplement éditer la #!ligne.

    Et vous avez encore tous les autres problèmes que l'utilisation #!/usr/bin/envvous donne.

    @jlliagre suggère dans un commentaire #!/usr/bin/envutile de tester votre script avec plusieurs versions d'interpréteur, "en modifiant uniquement leur ordre PATH / PATH"

    Si vous avez besoin de faire cela, il est beaucoup plus facile d'avoir juste plusieurs #!lignes en haut de votre script (ce ne sont que des commentaires n'importe où sauf la toute première ligne) et de couper / copier-coller celui que vous voulez utiliser maintenant. la première ligne.

    Dans vi, ce serait aussi simple que de déplacer le curseur sur la #!ligne souhaitée, puis de taper dd1GPou Y1GP. Même avec un éditeur aussi simple que nanocela, il faudrait quelques secondes pour utiliser la souris pour copier et coller.


Dans l’ensemble, les avantages de l’utilisation #!/usr/bin/envsont au mieux minimes et ne compensent même pas les inconvénients. Même l'avantage de "commodité" est largement illusoire.

OMI, c'est une idée idiote promue par un type de programmeur particulier qui pense que les systèmes d'exploitation ne sont pas des solutions sur lesquelles il faut travailler, ils sont un problème sur lequel il faut travailler (ou, au mieux, être ignoré).

PS: voici un script simple pour changer l’interprète de plusieurs fichiers à la fois.

change-shebang.sh:

#!/bin/bash

interpreter="$1"
shift

if [ -z "$(type -P $interpreter)" ] ; then
  echo "Error: '$interpreter' is not executable." >&2
  exit 1
fi

if [ ! -d "$interpreter" ] && [ -x "$interpreter" ] ; then
  shebang='#!'"$(realpath -e $interpreter)" || exit 1
else
  shebang='#!'"$(type -P $interpreter)"
fi

for f in "$@" ; do
  printf "%s\n" 1 i "$shebang" . w | ed "$f"
done

Exécuter comme, par exemple, change-shebang.sh python2.7 *.pyouchange-shebang.sh $HOME/bin/my-experimental-ruby *.rb

cas
la source
4
Personne d'autre ici n'a mentionné le problème ARGV [0]. Et personne n'a mentionné le problème / path / to / env dans un formulaire qui adresse directement l'un des arguments de son utilisation (c'est-à-dire que bash ou perl pourraient se trouver à un emplacement inattendu).
cas
2
Le problème du chemin de l’interprète est facilement résolu par un administrateur système avec des liens symboliques ou par l’éditeur qui édite son script. Ce n’est certainement pas un problème assez important pour inciter les gens à accepter tous les autres problèmes causés par l’utilisation d’env sur la ligne shebang qui sont mentionnés ici et sur d’autres questions. Promouvoir une mauvaise solution de la même manière qu'encourager les cshscripts, c'est la promouvoir: cela fonctionne, mais il existe de bien meilleures alternatives.
cas
3
les non administrateurs peuvent demander à leur administrateur système de le faire. ou ils peuvent simplement éditer le script et changer la #!ligne.
cas
2
C'est précisément ce que env contribue à éviter: dépendre de connaissances techniques spécifiques à un administrateur système ou à un système d'exploitation, non liées à Python ou autre.
jeudi
1
Je voudrais pouvoir upvote ce mille fois, parce qu'il est en fait une excellente réponse de toute façon - si quelqu'un utilise /usr/bin/envet je dois me débarrasser localement ou si elles ne l' utilisent pas et je dois l' ajouter. Les deux cas peuvent se produire et, par conséquent, les scripts fournis dans cette réponse sont des outils potentiellement utiles dans la boîte à outils.
Jstine
8

Ajout d'un autre exemple ici:

L'utilisation envest également utile lorsque vous souhaitez partager des scripts entre plusieurs rvmenvironnements, par exemple.

Exécuter ceci sur la ligne de commande, montre quelle version de ruby ​​sera utilisée quand #!/usr/bin/env rubyest utilisé dans un script:

env ruby --version

Par conséquent, lorsque vous utilisez env, vous pouvez utiliser différentes versions de ruby ​​via rvm, sans modifier vos scripts.

Pas maintenant
la source
1
// , Excellente idée. Le but de plusieurs interprètes n'est PAS de casser le code, ni de faire dépendre du code sur cet interpréteur spécifique .
Nathan Basanese
4

Si vous écrivez uniquement pour vous-même ou pour votre travail et que l'emplacement de l'interprète que vous appelez est toujours au même endroit, utilisez le chemin direct. Dans tous les autres cas, utilisez #!/usr/bin/env.

Voici pourquoi: dans votre cas, l’ pythoninterprète se trouvait au même endroit, quelle que soit la syntaxe que vous utilisiez, mais pour de nombreuses personnes, il aurait pu s’installer à un endroit différent. Bien que la plupart des principaux interprètes de programmation se trouvent dans de /usr/bin/nombreux logiciels plus récents, la configuration par défaut en est une /usr/local/bin/.

Je dirais même de toujours utiliser #!/usr/bin/envparce que si vous avez plusieurs versions du même interpréteur installées et que vous ne savez pas à quoi votre shell aura par défaut, vous devriez probablement résoudre ce problème.

Mauvis Ledford
la source
5
"Dans tous les autres cas, utilisez #! / Usr / bin / env" est trop fort. // Comme @KeithThompson et d'autres le font remarquer, #! / Usr / bin / env signifie que le script peut se comporter différemment selon qui / comment est exécuté. Parfois, ce comportement différent peut être un bug de sécurité. // Par exemple, n'utilisez JAMAIS "#! / Usr / bin / env python" pour un script setuid ou setgid, car l'utilisateur qui appelle le script peut placer un programme malveillant dans un fichier appelé "python" dans PATH. La question de savoir si les scripts setuid / gid sont ou non une bonne idée est un problème différent, mais aucun exécutable setuid / gid ne doit en aucun cas faire confiance à l'environnement fourni par l'utilisateur.
Krazy Glew
@KrazyGlew, vous ne pouvez pas définir un script sous Linux. Vous le faites via un exécutable. Et lors de l'écriture de cet exécutable, il est judicieux, et largement répandu, d'effacer les variables d'environnement. Certaines variables d'environnement sont également délibérément ignorées .
MayeulC
3

Pour des raisons de portabilité et de compatibilité, il est préférable d'utiliser

#!/usr/bin/env bash

au lieu de

#!/usr/bin/bash

Il existe plusieurs possibilités, où un binaire peut être situé sur un système Linux / Unix. Consultez la page de manuel hier (7) pour une description détaillée de la hiérarchie du système de fichiers.

FreeBSD, par exemple, installe tous les logiciels, qui ne font pas partie du système de base, dans / usr / local / . Étant donné que bash ne fait pas partie du système de base, le binaire bash est installé dans / usr / local / bin / bash .

Si vous voulez un script portable bash / csh / perl / que vous utilisiez sous la plupart des distributions Linux et FreeBSD, vous devez utiliser #! / Usr / bin / env .

Notez également que la plupart des installations Linux ont également (durement) lié le fichier binaire env à / bin / env ou les liens logiciels / usr / bin au logiciel / bin, ce qui ne devrait pas être utilisé dans le shebang. Donc, n'utilisez pas #! / Bin / env .

Mirko Steiner
la source