¿Por qué es mejor usar "#! / Usr / bin / env NAME" en lugar de "#! / Path / to / NAME" como mi shebang?

459

Noté que algunos scripts que he adquirido de otros tienen el shebang #!/path/to/NAMEmientras que otros (usando la misma herramienta, NAME) tienen el shebang #!/usr/bin/env NAME.

Ambos parecen funcionar correctamente. En los tutoriales (en Python, por ejemplo), parece haber una sugerencia de que este último shebang es mejor. Pero, no entiendo por qué esto es así.

Me doy cuenta de que, para usar el último shebang, NAME debe estar en la RUTA, mientras que el primer shebang no tiene esta restricción.

Además, parece (para mí) que el primero sería el mejor shebang, ya que especifica con precisión dónde se encuentra NAME. Entonces, en este caso, si hay varias versiones de NAME (por ejemplo, / usr / bin / NAME, / usr / local / bin / NAME), el primer caso especifica cuál usar.

Mi pregunta es ¿por qué se prefiere el primer shebang al segundo?

TheGeeko61
fuente
12
Ver esta respuesta ...
jasonwryan
@ TheGeeko61: En mi caso, tenía algo roto y algunas variables no estaban en env. Así que sugiero usar este shebang para verificar si env está cargado correctamente.
Gigamegs

Respuestas:

462

No es necesariamente mejor.

La ventaja de esto #!/usr/bin/env pythones que usará cualquier pythonejecutable que aparezca primero en el usuario $PATH.

La desventaja de #!/usr/bin/env pythones que va a utilizar cualquier pythonejecutable aparece por primera vez en el usuario de $PATH.

Eso significa que el script podría comportarse de manera diferente dependiendo de quién lo ejecute. Para un usuario, podría usar el /usr/bin/pythonque se instaló con el sistema operativo. Por otro lado, podría usar un experimental /home/phred/bin/pythonque no funciona correctamente.

Y si pythonsólo se instala en /usr/local/bin, un usuario que no tiene /usr/local/binen $PATHni siquiera será capaz de ejecutar el script. (Eso probablemente no sea muy probable en los sistemas modernos, pero podría suceder fácilmente para un intérprete más oscuro).

Al especificar #!/usr/bin/python, especifica exactamente qué intérprete se utilizará para ejecutar el script en un sistema en particular .

Otro problema potencial es que el #!/usr/bin/envtruco no le permite pasar argumentos al intérprete (aparte del nombre del script, que se pasa implícitamente). Esto generalmente no es un problema, pero puede serlo. Muchos scripts de Perl están escritos con #!/usr/bin/perl -w, pero use warnings;es el reemplazo recomendado en estos días. Los scripts Csh deberían usar #!/bin/csh -f, pero los scripts csh no se recomiendan en primer lugar. Pero podría haber otros ejemplos.

Tengo varias secuencias de comandos Perl en un sistema de control de fuente personal que instalo cuando configuro una cuenta en un nuevo sistema. Utilizo un script de instalador que modifica la #!línea de cada script a medida que lo instala en mi $HOME/bin. (No he tenido que usar nada más que #!/usr/bin/perlúltimamente; se remonta a los tiempos en que Perl a menudo no se instalaba de manera predeterminada).

Un punto menor: el #!/usr/bin/envtruco es posiblemente un abuso del envcomando, que originalmente tenía la intención (como su nombre lo indica) de invocar un comando con un entorno alterado. Además, algunos sistemas más antiguos (incluido SunOS 4, si no recuerdo mal) no tenían el envcomando /usr/bin. Es probable que ninguno de estos sea una preocupación importante. envfunciona de esta manera, muchos scripts utilizan el #!/usr/bin/envtruco, y es probable que los proveedores de sistemas operativos no hagan nada para romperlo. Que podría ser un problema si usted quiere que su secuencia de comandos para ejecutar en un sistema muy viejo, pero entonces es muy probable que tenga que modificar todos modos.

Otro posible problema, (gracias a Sopalajo de Arrierez por señalarlo en los comentarios) es que los trabajos cron se ejecutan con un entorno restringido. En particular, $PATHes típicamente algo así /usr/bin:/bin. Entonces, si el directorio que contiene el intérprete no se encuentra en uno de esos directorios, incluso si está en su $PATHshell predeterminado de usuario, entonces el /usr/bin/envtruco no funcionará. Puede especificar la ruta exacta, o puede agregar una línea a su crontab para establecer $PATH( man 5 crontabpara más detalles).

Keith Thompson
fuente
44
Si / usr / bin / perl es perl 5.8, $ HOME / bin / perl es 5.12, y un script que requiere 5.12 hardcodes / usr / bin / perl en los shebangs, puede ser un gran problema ejecutar el script. Raramente he visto que / usr / bin / env perl agarrar perl de la RUTA sea un problema, pero a menudo es muy útil. ¡Y es mucho más bonito que el truco ejecutivo!
William Pursell el
8
Vale la pena señalar que, si desea utilizar una versión de intérprete específica, / usr / bin / env es aún mejor. simplemente porque generalmente hay varias versiones de intérpretes instaladas en su máquina llamadas perl5, perl5.12, perl5.10, python3.3, python3.32, etc. y si su aplicación solo se ha probado en esa versión específica, aún puede especificar #! / usr / bin / env perl5.12 y esté bien incluso si el usuario lo tiene instalado en algún lugar inusual. En mi experiencia, 'python' generalmente es solo un enlace simbólico a la versión estándar del sistema (no necesariamente la versión más reciente del sistema).
raíz
66
@root: Para Perl, use v5.12;sirve para algo de ese propósito. Y #!/usr/bin/env perl5.12fallará si el sistema tiene Perl 5.14 pero no 5.12. Para Python 2 vs. 3, #!/usr/bin/python2y #!/usr/bin/python3es probable que funcionen.
Keith Thompson
3
@GoodPerson: Supongamos que escribo un script de Perl para instalarlo /usr/local/bin. Sé que funciona correctamente con /usr/bin/perl. No tengo idea de si funciona con cualquier perlejecutable que algún usuario aleatorio tenga en su cuenta $PATH. Quizás alguien esté experimentando con alguna versión antigua de Perl; porque especifiqué #!/usr/bin/perl, mi secuencia de comandos (que el usuario no necesariamente sabe ni le importa que sea una secuencia de comandos Perl) no dejará de funcionar.
Keith Thompson
55
Si no funciona /usr/bin/perl, lo descubriré muy rápidamente, y es responsabilidad del propietario / administrador del sistema mantenerlo actualizado. Si desea ejecutar mi script con su propio perl, siéntase libre de tomar y modificar una copia o invocarlo a través de perl foo. (Y podría considerar la posibilidad de que las 55 personas que votaron por esta respuesta también sepan una o dos cosas. Ciertamente es posible que tenga razón y todos estén equivocados, pero esa no es la forma en que apostaría.)
Keith Thompson
101

Porque / usr / bin / env puede interpretar tu $PATH, lo que hace que los scripts sean más portátiles.

#!/usr/local/bin/python

Solo ejecutará su script si python está instalado en / usr / local / bin.

#!/usr/bin/env python

Interpretará su $PATH, y encontrará python en cualquier directorio en su $PATH.

Por lo tanto, su script es más portátil y funcionará sin modificaciones en los sistemas donde python está instalado como /usr/bin/python, o /usr/local/bin/python, o incluso en directorios personalizados (que se han agregado a $PATH), como /opt/local/bin/python.

La portabilidad es la única razón por la que envse prefiere el uso a las rutas codificadas.

Tim Kennedy
fuente
19
Los directorios personalizados para pythonejecutables son particularmente comunes a medida que virtualenvaumenta el uso.
Xiong Chiamiov
1
¿Qué pasa #! python, por qué no se usa eso?
Kristianp
66
#! pythonno se usa porque tendría que estar en el mismo directorio que el binario de Python, ya que la palabra básica pythonse interpreta como la ruta completa al archivo. Si no tiene un binario de Python en el directorio actual, obtendrá un error como bash: ./script.py: python: bad interpreter: No such file or directory. Es lo mismo que si usaras#! /not/a/real/path/python
Tim Kennedy,
47

Especificar la ruta absoluta es más precisa en un sistema dado. La desventaja es que es demasiado preciso. Suponga que se da cuenta de que la instalación del sistema de Perl es demasiado antigua para sus scripts y desea utilizar el suyo en su lugar: luego tiene que editar los scripts y cambiar #!/usr/bin/perla #!/home/myname/bin/perl. Peor aún, si tiene Perl en /usr/binalgunas máquinas, /usr/local/binen otras y /home/myname/bin/perlen otras máquinas, deberá mantener tres copias separadas de los scripts y ejecutar la correspondiente en cada máquina.

#!/usr/bin/envse rompe si PATHes malo, pero también lo hace casi todo. Intentar operar con un mal PATHrara vez es útil e indica que sabe muy poco sobre el sistema en el que se ejecuta el script, por lo que no puede confiar en ninguna ruta absoluta de todos modos.

Hay dos programas cuya ubicación puede confiar en casi todas las variantes de Unix: /bin/shy /usr/bin/env. Algunas variantes de Unix oscuras y en su mayoría retiradas tenían /bin/envsin tener /usr/bin/env, pero es poco probable que las encuentres. Los sistemas modernos tienen /usr/bin/envprecisamente debido a su uso generalizado en shebangs. /usr/bin/enves algo con lo que puedes contar.

Además /bin/sh, la única vez que debe usar una ruta absoluta en un shebang es cuando su script no debe ser portátil, por lo que puede contar con una ubicación conocida para el intérprete. Por ejemplo, un script bash que solo funciona en Linux puede usarse de manera segura #!/bin/bash. Un script que solo debe usarse internamente puede basarse en las convenciones de ubicación del intérprete interno.

#!/usr/bin/envTiene inconvenientes. Es más flexible que especificar una ruta absoluta, pero aún requiere conocer el nombre del intérprete. Ocasionalmente, es posible que desee ejecutar un intérprete que no esté en $PATH, por ejemplo, en una ubicación relativa al script. En tales casos, a menudo puede crear un script políglota que puede ser interpretado tanto por el shell estándar como por el intérprete deseado. Por ejemplo, para hacer que un script de Python 2 sea portátil tanto en sistemas donde pythonestá Python 3 y python2Python 2 como en sistemas donde pythonestá Python 2 y python2no existe:

#!/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
fuente
22

Específicamente para Perl, usar #!/usr/bin/enves una mala idea por dos razones.

Primero, no es portátil. En algunas plataformas oscuras, env no está en / usr / bin. En segundo lugar, como ha señalado Keith Thompson , puede causar problemas para pasar argumentos en la línea shebang. La solución máximamente portátil es esta:

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

Para obtener detalles sobre cómo funciona, consulte 'perldoc perlrun' y lo que dice sobre el argumento -x.

DrHyde
fuente
Exactamente el anser que estaba buscando: cómo escribir "shebang" porable para el script perl que permitirá pasar argumentos adicionales a perl (la última línea en su ejemplo acepta argumentos adicionales).
AmokHuginnsson
15

La razón por la que hay una distinción entre los dos es por cómo se ejecutan los scripts.

Se requiere el uso /usr/bin/env(que, como se mencionó en otras respuestas, no está en /usr/binalgunos sistemas operativos) porque no puede simplemente poner un nombre ejecutable después de #!- debe ser una ruta absoluta. Esto se debe a que el #!mecanismo funciona en un nivel más bajo que el shell. Es parte del cargador binario del núcleo. Esto puede ser probado. Ponga esto en un archivo y márquelo como ejecutable:

#!bash

echo 'foo'

Encontrará que imprime un error como este cuando intenta ejecutarlo:

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

Si un archivo está marcado como ejecutable y comienza con a #!, el núcleo (que no conoce $PATHo el directorio actual: estos son conceptos de usuario) buscará un archivo utilizando una ruta absoluta. Debido a que usar una ruta absoluta es problemático (como se menciona en otras respuestas), a alguien se le ocurrió un truco: puede ejecutar /usr/bin/env(que casi siempre está en esa ubicación) para ejecutar algo usando $PATH.

Tiffany Bennett
fuente
11

Hay dos problemas más con el uso #!/usr/bin/env

  1. No resuelve el problema de especificar la ruta completa al intérprete, simplemente lo mueve env.

    envno se garantiza más que esté dentro de lo /usr/bin/envque bashse garantiza que esté dentro /bin/basho python in /usr/bin/python.

  2. env sobrescribe ARGV [0] con el nombre del intérprete (por ejemplo, bash o python).

    Esto evita que aparezca el nombre de su script, por ejemplo, en la pssalida (o cambia cómo / dónde aparece) y hace que sea imposible encontrarlo, por ejemplo,ps -C scriptname.sh

[actualización 2016-06-04]

Y un tercer problema:

  1. Cambiar su RUTA es más trabajo que simplemente editar la primera línea de un guión, especialmente cuando el guión de tales ediciones es trivial. p.ej:

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

    Anexar o pre-pendiente un directorio a $ PATH es bastante fácil (aunque todavía tiene que editar un archivo para que sea permanente, su ~/.profileo lo que sea) y está lejos de ser fácil de escribir ESE edición porque la RUTA podría establecerse en cualquier parte del script , no en la primera línea).

    Cambiar el orden de los directorios PATH es significativamente más difícil ... y mucho más difícil que simplemente editar la #!línea.

    Y todavía tiene todos los otros problemas que #!/usr/bin/envle da el uso .

    @jlliagre sugiere en un comentario que #!/usr/bin/enves útil para probar su secuencia de comandos con múltiples versiones de intérpretes, "solo cambiando su orden PATH / PATH"

    Si necesita hacer eso, es mucho más fácil tener varias #!líneas en la parte superior de su script (son solo comentarios en cualquier lugar menos la primera línea) y cortar / copiar y pegar la que desea usar ahora para La primera línea.

    En vi, eso sería tan simple como mover el cursor al#! línea que desea, luego escribir dd1GPo Y1GP. Incluso con un editor tan trivial como nano, usaría el mouse para copiar y pegar tomaría segundos.


En general, las ventajas de usar #!/usr/bin/envson mínimas en el mejor de los casos, y ciertamente ni siquiera se acercan a superar las desventajas. Incluso la ventaja de "conveniencia" es en gran medida ilusoria.

En mi opinión, es una idea tonta promovida por un tipo particular de programador que cree que los sistemas operativos no son algo con lo que se pueda trabajar, son un problema que se debe solucionar (o ignorar en el mejor de los casos).

PD: aquí hay un script simple para cambiar el intérprete de varios archivos a la vez.

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

Ejecútelo como, por ejemplo, change-shebang.sh python2.7 *.pyochange-shebang.sh $HOME/bin/my-experimental-ruby *.rb

cas
fuente
44
Nadie más aquí ha mencionado el problema ARGV [0]. Y nadie ha mencionado el problema / path / to / env en una forma que aborde directamente uno de los argumentos para usarlo (es decir, que bash o perl podrían estar en una ubicación inesperada).
cas
2
El problema de la ruta del intérprete se soluciona fácilmente por un administrador de sistemas con enlaces simbólicos, o por el usuario que edita su secuencia de comandos. Ciertamente, no es un problema lo suficientemente significativo como para alentar a las personas a soportar todos los otros problemas causados ​​por el uso de env en la línea shebang que se mencionan aquí y en otras preguntas. Eso es promover una mala solución de la misma manera que alentar la cshcreación de secuencias de comandos es promover una mala solución: funciona, pero hay alternativas mucho mejores.
cas
3
los que no son administradores de sistemas pueden pedirle a sus administradores de sistemas que lo hagan. o simplemente pueden editar el guión y cambiar la #!línea.
cas
2
Eso es precisamente lo que env está ayudando a evitar: dependiendo de un sysadmin o sistema operativo conocimiento técnico específico no relacionado con python o lo que sea.
jlliagre
1
Desearía poder votar esto miles de veces, porque en realidad es una gran respuesta de cualquier manera : si alguien usa /usr/bin/envy necesito deshacerme de él localmente, o si no lo usaron y necesito agregarlo. Ambos casos pueden ocurrir y, por lo tanto, los scripts proporcionados en esta respuesta son una herramienta potencialmente útil para tener en la caja de herramientas.
jstine
8

Añadiendo otro ejemplo aquí:

El uso envtambién es útil cuando desea compartir scripts entre múltiples rvmentornos, por ejemplo.

Al ejecutar esto en la línea cmd, se muestra qué versión de ruby ​​se usará cuando #!/usr/bin/env rubyse use dentro de un script:

env ruby --version

Por lo tanto, cuando usa env, puede usar diferentes versiones de ruby ​​a través de rvm, sin cambiar sus scripts.

Ahora no
fuente
1
// , Excelente idea. El objetivo de múltiples intérpretes NO es romper el código, o que el código dependa de ese intérprete específico .
Nathan Basanese
4

Si está escribiendo exclusivamente para usted o para su trabajo y la ubicación del intérprete al que llama siempre está en el mismo lugar, utilice la ruta directa. En todos los demás casos, use #!/usr/bin/env.

He aquí por qué: en su situación, el pythonintérprete estaba en el mismo lugar, independientemente de la sintaxis que utilizó, pero para muchas personas podría haberse instalado en un lugar diferente. Aunque la mayoría de los principales intérpretes de programación se encuentran en /usr/bin/una gran cantidad de software más nuevo, está predeterminado /usr/local/bin/.

Incluso argumentaría que siempre se #!/usr/bin/envdebe usar porque si tiene instaladas varias versiones del mismo intérprete y no sabe cuál será el valor predeterminado de su shell, entonces probablemente debería solucionarlo.

Mauvis Ledford
fuente
55
"En todos los demás casos, usar #! / Usr / bin / env" es demasiado fuerte. // Como @KeithThompson y otros señalan, #! / Usr / bin / env significa que el script puede comportarse de manera diferente dependiendo de quién / cómo se ejecute. A veces, este comportamiento diferente puede ser un error de seguridad. // Por ejemplo, NUNCA use "#! / Usr / bin / env python" para un script setuid o setgid, porque el usuario que invoca el script puede colocar malware en un archivo llamado "python" en PATH. Si los scripts setuid / gid son siempre una buena idea es un tema diferente, pero ciertamente, ningún ejecutable setuid / gid debería confiar en el entorno proporcionado por el usuario.
Krazy Glew
@KrazyGlew, no puede configurar un script en Linux. Lo haces a través de un ejecutable. Y al escribir este ejecutable, es una buena práctica, y ampliamente hecho, borrar las variables de entorno. Algunas variables de entorno también se ignoran a propósito .
MayeulC
3

Por razones de portabilidad y compatibilidad, es mejor usar

#!/usr/bin/env bash

en lugar de

#!/usr/bin/bash

Hay múltiples posibilidades, donde un binario puede estar ubicado en un sistema Linux / Unix. Consulte la página de manual de hier (7) para obtener una descripción detallada de la jerarquía del sistema de archivos.

FreeBSD, por ejemplo, instala todo el software, que no es parte del sistema base, en / usr / local / . Dado que bash no es parte del sistema base, el binario bash se instala en / usr / local / bin / bash .

Cuando desee un script portátil bash / csh / perl / whatever, que se ejecuta en la mayoría de las distribuciones de Linux y FreeBSD, debe usar #! / Usr / bin / env .

También tenga en cuenta que la mayoría de las instalaciones de Linux también han vinculado (duro) el binario env a / bin / env o softlinked / usr / bin a / bin que no debe usarse en el shebang. Así que no uses #! / Bin / env .

Mirko Steiner
fuente