¿Cuál es la diferencia entre "#! / Usr / bin / env bash" y "#! / Usr / bin / bash"?

372

En el encabezado de un script Bash, ¿cuál es la diferencia entre esas dos declaraciones:

  1. #!/usr/bin/env bash

  2. #!/usr/bin/bash

Cuando consulté la env página de manual , obtuve esta definición:

 env - run a program in a modified environment

Qué significa eso?

tarrsalah
fuente
77
Vea esta pregunta y mi respuesta .
Keith Thompson
66
¿Quién puede decirme por qué esta pregunta está cerrada, "relacionada con la programación o el desarrollo de software" no?
tarrsalah
1
Estoy de acuerdo en que no está fuera de tema, pero probablemente sea un duplicado de varias otras preguntas como esta .
Keith Thompson
16
Esta pregunta no debería haber sido marcada como fuera de tema. Solo necesita 5 personas con un puntaje superior a 3000 para marcarlo como "sobre el tema" y se puede volver a abrir. Es una pregunta, específicamente sobre programación.
Danijel-James W
3
Estoy sorprendido Sorprendido al descubrir que la documentación de Linux está plagada de tautologías. xkcd.com/703 git-man-page-generator.lokaltog.net
allyourcode

Respuestas:

323

Ejecutar un comando /usr/bin/envtiene la ventaja de buscar cualquiera que sea la versión predeterminada del programa en su entorno actual .

De esta manera, no tiene que buscarlo en un lugar específico del sistema, ya que esas rutas pueden estar en diferentes ubicaciones en diferentes sistemas. Mientras esté en tu camino, lo encontrará.

Una desventaja es que no podrá pasar más de un argumento (por ejemplo, no podrá escribir /usr/bin/env awk -f) si desea admitir Linux, ya que POSIX es impreciso sobre cómo se interpretará la línea, y Linux interpreta todo después del primer espacio para denotar un solo argumento. Puede usar /usr/bin/env -Salgunas versiones de envpara evitar esto, pero luego el script se volverá aún menos portátil y se romperá en sistemas bastante recientes (por ejemplo, incluso Ubuntu 16.04 si no más tarde).

Otro inconveniente es que, dado que no está llamando a un ejecutable explícito, tiene el potencial de errores y problemas de seguridad de sistemas multiusuario (si alguien logró que su ejecutable sea llamado bashen su ruta, por ejemplo).

#!/usr/bin/env bash #lends you some flexibility on different systems
#!/usr/bin/bash     #gives you explicit control on a given system of what executable is called

En algunas situaciones, puede preferirse el primero (como ejecutar scripts de python con múltiples versiones de python, sin tener que volver a trabajar la línea ejecutable). Pero en situaciones donde la seguridad es el foco, se preferiría lo último, ya que limita las posibilidades de inyección de código.

Alec Bennett
fuente
22
Otro inconveniente es que no puede pasar un argumento adicional al intérprete.
Keith Thompson
1
@KeithThompson: información incorrecta ... ¡Puede pasar opciones al intérprete subyacente usando / usr / bin / env!
Gaurav Agarwal
44
@GauravAgarwal: No en mi sistema. Una secuencia de comandos que contiene sólo por esta sola línea: #!/usr/bin/env echo Hellose queja: /usr/bin/env: echo Hello: No such file or directory. Aparentemente se trata echo Hellocomo un argumento único para /usr/bin/env.
Keith Thompson,
1
@ AndréLaszlo: El envcomando ciertamente permite pasar argumentos al comando. El problema es la semántica de la #!línea, y eso depende del núcleo. Los núcleos de Linux recientes permiten cosas como #!/usr/bin/env command args, pero los núcleos de Linux más antiguos y otros sistemas no.
Keith Thompson
1
¿Por qué hay backticks en el bloque de código? ¿No deberían ser eliminados?
Benjamin W.
64

El uso #!/usr/bin/env NAMEhace que el shell busque la primera coincidencia de NAME en la variable de entorno $ PATH. Puede ser útil si no conoce la ruta absoluta o no desea buscarla.

Dolphiniac
fuente
99
Al menos tienes que saber dónde está env :).
ᐅ devrimbaris el
1
Excelente respuesta Explica sucintamente lo que hace env shebang, en lugar de decir "elige el programa en función de la configuración de su sistema"
De Novo
13

En lugar de definir explícitamente la ruta al intérprete como en /usr/bin/bash/, mediante el comando env, se busca el intérprete y se inicia desde donde se encuentre por primera vez. Esto tiene ventajas y desventajas

safay
fuente
En la mayoría de los sistemas, serán funcionalmente iguales, pero depende de la ubicación de sus ejecutables bash y env. Sin embargo, no estoy seguro de cómo esto afectará a las variables de entorno.
safay
2
"Es posible especificar el intérprete sin usar env, dando la ruta completa al intérprete. Un problema es que en diferentes sistemas informáticos, la ruta exacta puede ser diferente. Al usar env, el intérprete se busca y se encuentra en el momento en que se ejecuta el script. Esto hace que el script sea más portátil, pero también aumenta el riesgo de que se seleccione un intérprete incorrecto porque busca una coincidencia en cada directorio en la ruta de búsqueda ejecutable. También tiene el mismo problema de que el el camino hacia el entorno binario también puede ser diferente para cada máquina. "- Wikipedia
Mike Clark
10

Si los scripts de shell comienzan con #!/bin/bash, siempre se ejecutarán con bashfrom /bin. Sin embargo, si comienzan con #!/usr/bin/env bash, buscarán bashadentro $PATHy luego comenzarán con el primero que puedan encontrar.

¿Por qué sería útil esto? Suponga que desea ejecutar bashscripts, que requieren bash 4.xo posterior, pero su sistema solo tiene bash3.x instalado y actualmente su distribución no ofrece una versión más nueva o no es administrador y no puede cambiar lo que está instalado en ese sistema .

Por supuesto, puede descargar el código fuente de bash y crear su propio bash desde cero, ~/binpor ejemplo. Y también puede modificar su $PATHvariable en su .bash_profilearchivo para incluirla ~/bincomo la primera entrada ( PATH=$HOME/bin:$PATHya ~que no se expandirá $PATH). Si ahora llama bash, el shell primero lo buscará $PATHen orden, por lo que comienza con ~/bindónde encontrará su bash. Lo mismo sucede si los scripts buscan bashusar #!/usr/bin/env bash, por lo que estos scripts ahora estarían funcionando en su sistema usando su bashcompilación personalizada .

Una desventaja es que esto puede conducir a un comportamiento inesperado, por ejemplo, el mismo script en la misma máquina puede ejecutarse con diferentes intérpretes para diferentes entornos o usuarios con diferentes rutas de búsqueda, causando todo tipo de dolores de cabeza.

El mayor inconveniente enves que algunos sistemas solo permitirán un argumento, por lo que no puede hacer esto #!/usr/bin/env <interpreter> <arg>, ya que los sistemas verán <interpreter> <arg>como un solo argumento (lo tratarán como si se citara la expresión) y, por envlo tanto, buscarán un intérprete llamado <interpreter> <arg>. Tenga en cuenta que este no es un problema del envcomando en sí mismo, que siempre permitió pasar múltiples parámetros, pero con el analizador shebang del sistema que analiza esta línea incluso antes de llamar env. Mientras tanto, esto se ha solucionado en la mayoría de los sistemas, pero si su script quiere ser ultra portátil, no puede confiar en que esto se haya solucionado en el sistema que ejecutará.

Incluso puede tener implicaciones de seguridad, por ejemplo, si sudono se configuró para limpiar el entorno o si $PATHse excluyó de la limpieza. Déjame demostrarte esto:

Por /binlo general, es un lugar bien protegido, solo rootes capaz de cambiar algo allí. Sin embargo, su directorio de inicio no es ningún programa que ejecute que pueda realizar cambios en él. Eso significa que el código malicioso podría colocar una falsificación bashen algún directorio oculto, modificar su .bash_profilepara incluir ese directorio en su $PATH, de modo que todos los scripts que se utilicen #!/usr/bin/env bashterminarán ejecutándose con esa falsificación bash. Si se sudomantiene $PATH, estás en un gran problema.

Por ejemplo, considere que una herramienta crea un archivo ~/.evil/bashcon el siguiente contenido:

#!/bin/bash

if [ $EUID -eq 0 ]; then
  echo "All your base are belong to us..."
  # We are root - do whatever you want to do
fi

/bin/bash "$@"

Hagamos un script simple sample.sh:

#!/usr/bin/env bash

echo "Hello World"

Prueba de concepto (en un sistema donde se sudoguarda $PATH):

$ ./sample.sh
Hello World

$ sudo ./sample.sh
Hello World

$ export PATH="$HOME/.evil:$PATH"

$ ./sample.sh
Hello World

$ sudo ./sample.sh
All your base are belong to us...
Hello World

Por lo general, las conchas clásicas deben ubicarse todas /biny si no desea colocarlas allí por cualquier razón, realmente no es un problema colocar un enlace simbólico en /binesos puntos a sus ubicaciones reales (o tal vez en /binsí mismo es un enlace simbólico), por lo que Yo siempre iría con #!/bin/shy #!/bin/bash. Hay demasiado que se rompería si esto ya no funcionara. No es que POSIX requiera esta posición (POSIX no estandariza los nombres de ruta y, por lo tanto, ni siquiera estandariza la función shebang), sino que son tan comunes que incluso si un sistema no ofreciera un /bin/sh, probablemente todavía entendería #!/bin/shy saber qué hacer con él y solo puede ser por compatibilidad con el código existente.

Pero para intérpretes opcionales más modernos, no estándar, como Perl, PHP, Python o Ruby, en realidad no se especifica en ningún lugar donde deberían ubicarse. Pueden estar en /usr/binpero pueden también estar en /usr/local/bino en una rama jerarquía completamente diferente ( /opt/..., /Applications/..., etc.). Es por eso que a menudo usan la #!/usr/bin/env xxxsintaxis shebang.

Mecki
fuente
4

Lo encuentro útil, porque cuando no sabía sobre env, antes de comenzar a escribir el script, estaba haciendo esto:

type nodejs > scriptname.js #or any other environment

y luego estaba modificando esa línea en el archivo en shebang.
Estaba haciendo esto, porque no siempre recordaba dónde está nodejs en mi computadora: / usr / bin / o / bin /, por lo que para mí enves muy útil. Tal vez hay detalles con esto, pero esta es mi razón

sudo97
fuente