web-dev-qa-db-fra.com

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

Je remarque que certains scripts que j'ai acquis auprès d'autres ont le Shebang #!/path/to/NAME tandis 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 il en est ainsi.

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 précise précisément où se trouve NAME. Donc, dans ce cas, s'il existe plusieurs versions de NAME (par exemple,/usr/bin/NAME,/usr/local/bin/NAME), le premier cas spécifie lequel utiliser.

Ma question est pourquoi le premier Shebang est-il préféré au second?

497
TheGeeko61

Critères/exigences objectifs:

Pour déterminer s'il faut utiliser un absolu ou chemin logique (/usr/bin/env) vers un interprète lors d'une transe, il y a (2) considérations clés:

a) L'interprète se trouve sur le système cible

b) La version correcte de interprète peut être trouvé sur le système cible

Si nous [~ # ~] convenons [~ # ~] que " b) "est souhaitable, nous convenons également que :

c) Il est préférable que nos scripts échouent plutôt que d'exécuter en utilisant une version d'interpréteur incorrecte et potentiellement obtenir des résultats incohérents .

Si nous NE CONVENONS PAS que " b) " est important, alors tout interprète trouvé suffira.

Essai:

Depuis l'utilisation d'un chemin logique - /usr/bin/env Vers l'interprète dans la transe est la solution la plus extensible permettant au même script de s'exécuter avec succès sur des hôtes cibles avec des chemins différents vers le même interprète, nous le testerons en utilisant Python en raison de sa 'popularité- pour voir s'il répond à nos critères.

  1. Est-ce que /usr/bin/env Vit dans un emplacement prévisible et cohérent sur [~ # ~] populaire [~ # ~] (pas " tous les ") systèmes d'exploitation? Oui :

    • RHEL 7.5
    • Ubuntu 18.04
    • Raspbian 10 ("Buster")
    • OSX 10.15.02
  2. Ci-dessous Python exécuté à l'intérieur et à l'extérieur des enveloppes virtuelles ( Pipenv utilisé) lors des tests:

    #!/usr/bin/env pythonX.x import sys print(sys.version) print('Hello, world!')

  3. La transgression du script a été basculée par Python souhaité (tous installés sur le même hôte):

    • #!/usr/bin/env python2
    • #!/usr/bin/env python2.7
    • #!/usr/bin/env python3
    • #!/usr/bin/env python3.5
    • #!/usr/bin/env python3.6
    • #!/usr/bin/env python3.7
  4. Résultats attendus: cette print(sys.version) = env pythonX.x. Chaque fois que ./test1.py A été exécuté en utilisant une version installée différente Python, la version correcte spécifiée dans le she-bang a été imprimée.

  5. Notes de test:

    • Les tests étaient exclusivement limités à Python
    • Perl: Comme Python- ( [~ # ~] doit [~ # ~] vivre dans /usr/bin Selon le FHS
    • Je n'ai pas essayé toutes les combinaisons possibles sur tous les nombres possibles de systèmes d'exploitation Linuxy/Unixy et de versions de chaque système d'exploitation.

Conclusion:

Bien qu'il soit VRAI que #!/usr/bin/env python Utilise la première version de Python qu'il trouve dans le chemin de l'utilisateur, nous pouvons modérer ce comportement en spécifiant un numéro de version tel que #!/usr/bin/env pythonX.x En effet, les développeurs ne se soucient pas de l’interpréteur qui se trouve " en premier ", tout ce qui leur tient à cœur est que leur code soit exécuté à l’aide de l’interpréteur spécifié qu’il sait être compatible avec leur code pour assurer des résultats cohérents - partout où cela peut vivre dans le système de fichiers ...

En termes de portabilité/flexibilité, utiliser un logique - /usr/bin/env - plutôt que chemin absolu non seulement satisfait aux exigences a), b) et c) de mes tests avec différentes versions de Python , mais a également l'avantage de la logique floue de trouver le même interpréteur de version même s'ils vivent sur des chemins différents sur différents systèmes d'exploitation. Et bien que [~ # ~] la plupart des distributions [~ # ~] respectent le FHS, toutes ne le font pas .

Donc, où un script échouera [~ # ~] échouera [~ # ~] si le binaire vit dans différents chemin absolu alors spécifié dans Shebang, le même script utilisant un logique chemin [~ # ~] réussit [~ # ~] car il continue jusqu'à ce qu'il trouve un match, offrant ainsi une plus grande fiabilité et extensibilité sur toutes les plateformes.

1
F1Linux

Ce n'est pas nécessairement mieux.

L'avantage de #!/usr/bin/env python Est qu'il utilisera tout l'exécutable python apparaissant en premier dans le $PATH De l'utilisateur.

L'inconvénient de #!/usr/bin/env python Est qu'il utilisera tout l'exécutable python apparaissant en premier dans l'utilisateur $PATH.

Cela signifie que le script peut se comporter différemment selon qui l'exécute. Pour un utilisateur, il peut utiliser le /usr/bin/python Installé avec le système d'exploitation. Pour un autre, il pourrait utiliser un /home/phred/bin/python Expérimental qui ne fonctionne pas tout à fait correctement.

Et si python n'est installé que dans /usr/local/bin, Un utilisateur qui n'a pas /usr/local/bin Dans $PATH Ne 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 pour un interprète plus obscur.)

En spécifiant #!/usr/bin/python, Vous 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'astuce #!/usr/bin/envne vous permet pas de passer des arguments à l'intrepreter (autre que le nom du script, qui est passé implicitement). Ce généralement n'est pas un problème, mais il peut l'être. De nombreux scripts Perl sont écrits avec #!/usr/bin/Perl -w, Mais use warnings; Est le remplacement recommandé de nos jours. Les scripts csh doivent utiliser #!/bin/csh -f - mais les scripts csh sont non 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 lors de son installation dans mon $HOME/bin. (Je n'ai pas eu à utiliser autre chose que #!/usr/bin/Perl Récemment; cela remonte à une époque où Perl n'était souvent pas installé par défaut.)

Un point mineur: l'astuce #!/usr/bin/env Est sans doute un abus de la commande env, qui était à l'origine destinée (comme son nom l'indique) à invoquer une commande avec un environnement modifié. De plus, certains systèmes plus anciens (y compris SunOS 4, si je me souviens bien) n'avaient pas la commande env dans /usr/bin. Aucun de ces éléments n'est susceptible d'être une préoccupation importante. env fonctionne de cette façon, de nombreux scripts utilisent l'astuce #!/usr/bin/env et les fournisseurs de système d'exploitation ne sont pas susceptibles de faire quoi que ce soit pour le casser. Cela pourrait être un problème si vous voulez que votre script s'exécute sur un système très ancien, mais vous devrez probablement le modifier de toute façon.

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 avec un environnement restreint. En particulier, $PATH Est 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 se trouve dans votre $PATH Par défaut dans un shell utilisateur, l'astuce /usr/bin/env Ne va pas travail. Vous pouvez spécifier le chemin exact, ou vous pouvez ajouter une ligne à votre crontab pour définir $PATH (man 5 crontab Pour plus de détails).

491
Keith Thompson

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

Interprétera votre $PATH, et recherchez python dans n'importe quel répertoire de votre $PATH.

Votre script est donc 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 des répertoires personnalisés (qui ont été ajoutés à $PATH), comme /opt/local/bin/python.

La portabilité est la seule raison pour laquelle l'utilisation de env est préférée aux chemins codés en dur.

105
Tim Kennedy

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 à la place: alors vous devez éditer les scripts et changer #!/usr/bin/Perl à #!/home/myname/bin/Perl. Pire, si vous avez Perl dans /usr/bin sur certaines machines, /usr/local/bin sur les autres et /home/myname/bin/Perl sur d'autres machines, vous devez alors conserver trois copies distinctes des scripts et exécuter celle qui convient sur chaque machine.

#!/usr/bin/env se casse si PATH est mauvais, mais presque tout. Tenter de fonctionner avec un mauvais PATH est très rarement utile et indique que vous en savez très peu sur le système sur lequel le script s'exécute, vous ne pouvez donc pas compter sur un chemin absolu de toute façon.

Il existe deux programmes dont vous pouvez vous fier à l'emplacement sur presque toutes les variantes Unix: /bin/sh et /usr/bin/env. Certaines variantes Unix obscures et pour la plupart à la retraite avaient /bin/env sans avoir /usr/bin/env, mais il est peu probable que vous les rencontriez. Les systèmes modernes ont /usr/bin/env précisément en raison de son utilisation répandue dans les shebangs. /usr/bin/env est une chose sur laquelle vous pouvez compter.

Excepté /bin/sh, la seule fois où vous devez utiliser un chemin absolu dans un Shebang est lorsque votre script n'est pas destiné à être portable, vous pouvez donc compter sur un emplacement connu pour l'interpréteur. Par exemple, un script bash qui ne fonctionne que sous Linux peut utiliser en toute sécurité #!/bin/bash. Un script qui est uniquement destiné à être utilisé en interne peut s'appuyer sur les conventions d'emplacement de l'interpréteur interne.

#!/usr/bin/env a 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éteur. Parfois, vous souhaiterez peut-être exécuter un interpréteur qui n'est pas dans le $PATH, par exemple dans un emplacement relatif au script. Dans de tels cas, vous pouvez souvent créer un script polyglotte qui peut être interprété à la fois par le shell standard et par l'interpréteur souhaité. Par exemple, pour rendre un script Python 2 portable sur les systèmes où python est Python 3 et python2 est Python 2, et pour les systèmes où python est Python 2 et python2 n'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 …

Spécifiquement pour Perl, en utilisant #!/usr/bin/env est 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 Keith Thompson l'a noté, cela peut causer des problèmes avec le passage d'arguments sur la ligne Shebang. La solution au maximum portable est la suivante:

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

Pour plus de détails sur son fonctionnement, voir "perldoc perlrun" et ce qu'il dit sur l'argument -x.

23
DrHyde

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

En utilisant /usr/bin/env (qui, comme mentionné dans d'autres réponses, n'est pas dans /usr/bin sur certains systèmes d'exploitation) est requis car vous ne pouvez pas simplement mettre un nom exécutable après le #! - ce doit être un chemin absolu. C'est parce que le #! Le mécanisme fonctionne à un niveau inférieur à celui du shell. Cela fait partie du chargeur binaire du noyau. Cela peut être testé. Mettez-le dans un fichier et marquez-le comme exécutable:

#!bash

echo 'foo'

Vous constaterez qu'il imprime 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 $PATH ou le répertoire courant: ce sont des concepts de terrain utilisateur) rechercheront 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 trouvé une astuce: vous pouvez exécuter /usr/bin/env (qui se trouve presque toujours à cet emplacement) pour exécuter quelque chose à l'aide de $PATH.

16
Tiffany Bennett

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

  1. Il ne résout pas le problème de spécifier le chemin complet vers l'interpréteur, il le déplace simplement vers env.

    env n'est pas plus garanti d'être dans /usr/bin/env que bash est garanti d'être dans /bin/bash ou python dans /usr/bin/python.

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

    Cela empêche le nom de votre script d'apparaître dans, par exemple, ps sortie (ou modifie comment/où il apparaît) et rend impossible de le trouver avec, par exemple, ps -C scriptname.sh

[mise à jour 2016-06-04]

Et un troisième problème:

  1. Changer votre PATH, c'est plus travailler que de simplement éditer la première ligne d'un script, surtout lorsque le scripting de telles modifications est trivial. par exemple:

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

    L'ajout ou la pré-attente d'un répertoire à $ PATH est assez facile (bien que vous ayez encore à éditer un fichier pour le rendre permanent - votre ~/.profile Ou quoi que ce soit - et il est loin d'être facile de scripter cette édition car le PATH pourrait ê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 beaucoup plus difficile que de simplement éditer la ligne #!.

    Et vous avez toujours tous les autres problèmes que l'utilisation de #!/usr/bin/env Vous pose.

    @jlliagre suggère dans un commentaire que #!/usr/bin/env est utile pour tester votre script avec plusieurs versions d'interpréteur, "en changeant uniquement leur ordre PATH/PATH"

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

    Dans vi, ce serait aussi simple que de déplacer le curseur sur la ligne #! Que vous voulez, puis de taper dd1GP Ou Y1GP. Même avec un éditeur aussi trivial que nano, il faudrait quelques secondes pour utiliser la souris pour copier et coller.


Dans l'ensemble, les avantages de l'utilisation de #!/usr/bin/env Sont au mieux minimes et ne sont certainement pas près de l'emporter sur les inconvénients. Même l'avantage de "commodité" est largement illusoire.

IMO, c'est une idée idiote promue par un type particulier de programmeur qui pense que les systèmes d'exploitation ne sont pas quelque chose avec lequel travailler, ils sont un problème à contourner (ou au mieux 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écutez-le comme, par exemple, change-Shebang.sh python2.7 *.py Ou change-Shebang.sh $HOME/bin/my-experimental-Ruby *.rb

11
cas

Ajout d'un autre exemple ici:

L'utilisation de env est également utile lorsque vous souhaitez partager des scripts entre plusieurs environnements rvm par exemple.

L'exécution de cette commande sur la ligne cmd indique quelle version Ruby sera utilisée lorsque #!/usr/bin/env Ruby est utilisé dans un script:

env Ruby --version

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

9
Not Now

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

Voici pourquoi: dans votre situation, l'interpréteur python était au même endroit, quelle que soit la syntaxe utilisée, mais pour de nombreuses personnes, il aurait pu être installé à un autre endroit. Bien que la plupart des principaux interprètes de programmation se trouvent dans /usr/bin/ de nombreux logiciels plus récents sont par défaut à /usr/local/bin/.

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

5
Mauvis Ledford

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 de multiples 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/. Puisque le bash ne fait pas partie du système de base, le binaire bash est installé dans /usr/local/bin/bash .

Lorsque vous voulez un script bash/csh/Perl/quel que soit portable, qui fonctionne sous la plupart des distributions Linux et FreeBSD, vous devez utiliser #!/Usr/bin/env .

Notez également que la plupart des installations Linux ont également (dur) lié le binaire env à/bin/env ou un lien logiciel/usr/bin dans/bin qui devrait pas être utilisé dans le Shebang. N'utilisez donc pas #!/Bin/env .

3
Mirko Steiner