¿Por qué el "-" en el "#! / bin / sh - "shebang?

28
#! / bin / sh -

Es (o al menos fue) a menudo el shebang recomendado para que un script sea interpretado /bin/sh.

¿Por qué no solo #! /bin/sho #!/bin/sh?

¿Para qué es eso -?

Stéphane Chazelas
fuente

Respuestas:

38

Eso es por una razón similar a la que necesita escribir:

rm -- *.txt

Y no

rm *.txt

A menos que pueda garantizar que ninguno de los .txtarchivos del directorio actual tenga un nombre que comience por -.

En:

rm <arg>

<arg>se considera una opción si comienza con -o un archivo para eliminar de lo contrario. En

rm - <arg>

argsiempre se considera como un archivo para eliminar, independientemente de si comienza con -o no.

Eso es lo mismo para sh.

Cuando uno ejecuta un script que comienza con

#! /bin/sh

Típicamente con:

execve("path/to/the-script", ["the-script", "arg"], [environ])

El sistema lo transforma a:

execve("/bin/sh", ["/bin/sh", "path/to/the-script", "arg"], [environ])

Por path/to/the-scriptlo general, no es algo que esté bajo el control del autor del script. El autor no puede predecir dónde se almacenarán las copias del guión ni con qué nombre. En particular, no pueden garantizar que la path/to/the-scriptcon la que se llama no comenzará -(o +que también es un problema sh). Es por eso que necesitamos el -aquí para marcar el final de las opciones.

Por ejemplo, en mi sistema zcat(como la mayoría de los otros scripts en realidad) es un ejemplo de un script que no siguió esa recomendación:

$ head -n1 /bin/zcat
#!/bin/sh
$ mkdir +
$ ln -s /bin/zcat +/
$ +/zcat
/bin/sh: +/: invalid option
[...]

Ahora puede preguntar por qué #! /bin/sh -y no #! /bin/sh --.

Si bien #! /bin/sh --funcionaría con shells POSIX, #! /bin/sh -es más portátil; en particular a las versiones antiguas de sh. shtratar -como y las fechas anteriores al final de la opción getopt()y el uso general de --para marcar el final de las opciones por un largo tiempo. La forma en que el shell Bourne (de finales de los 70) analizó sus argumentos, solo se consideró el primer argumento para las opciones si comenzaba con -. Todos los caracteres posteriores -se tratarán como nombres de opciones; Si no había ningún personaje después del -, no había opciones. Eso se atascó y todos los proyectiles similares a Bourne posteriores reconocen -como una forma de marcar el final de las opciones.

En el shell Bourne (pero no en los shells modernos tipo Bourne), #! /bin/sh -euftambién se solucionaría el problema, ya que solo se consideró el primer argumento para las opciones.

Ahora, uno podría decir que estamos siendo pedantes aquí, y también es por eso que escribí la necesidad en cursiva arriba:

  1. nadie en su sano juicio llamaría un script con algo que comience con -o +o los coloque en un directorio cuyo nombre comience con -o +.
  2. incluso si lo hicieran, primero uno argumentaría que solo pueden culparse a sí mismos, pero también, cuando invocas un script, la mayoría de las veces, es de un shell o de funciones execvp()/ execlp()-type. Y en ese caso, generalmente los invoca the-scriptpara que se busquen, en $PATHcuyo caso el argumento de la ruta a la execve()llamada del sistema generalmente comenzará con /(no -ni +) o como ./the-scriptsi desea the-scriptque se ejecute en el directorio actual (y entonces el camino comienza con ./, -ni +tampoco).

Ahora, además del problema de la corrección teórica , hay otra razón por la cual #! /bin/sh -se recomienda como buena práctica . Y eso se remonta a una época en la que varios sistemas todavía admitían scripts setuid.

Si tiene un script que contiene:

#! /bin/sh
/bin/echo "I'm running as root"

Y ese script era raíz setuid (como con -r-sr-xr-x root binpermisos), en esos sistemas, cuando es ejecutado por un usuario común,

execve("/bin/sh", ["/bin/sh", "path/to/the-script"], [environ])

se haría como root!

Si el usuario creó un enlace simbólico /tmp/-i -> path/to/the-scripty lo ejecutó como -i, entonces comenzaría un shell interactivo ( /bin/sh -i) como root.

Esto -funcionaría alrededor de eso (no funcionaría alrededor del problema de la condición de carrera , o el hecho de que algunas shimplementaciones como las ksh88basadas en algunas buscarían argumentos de script sin /en $PATH).

Hoy en día, casi ningún sistema admite el script setuid, y algunos de los que aún lo hacen (generalmente no de forma predeterminada), terminan haciendo un execve("/bin/sh", ["/bin/sh", "/dev/fd/<n>", arg])(donde <n>hay un descriptor de archivo abierto para leer en el script) que funciona tanto para este problema como para el condición de carrera.

Tenga en cuenta que tiene problemas similares con la mayoría de los intérpretes, no solo con los shells tipo Bourne. Los shells que no son de Bourne generalmente no son compatibles -con el marcador de fin de opción, pero generalmente son compatibles --(al menos para las versiones modernas).

también

#! /usr/bin/awk -f
#! /usr/bin/sed -f

¿No tiene el problema ya que el siguiente argumento es considerado como un argumento a la -fopción en cualquier caso, pero aún así no funciona si path/to/scriptes -(en cuyo caso, la mayoría de los sed/ awklas implementaciones, sed/ awkno lea el código de la -archivo, pero de stdin en su lugar).

También tenga en cuenta que en la mayoría de los sistemas, uno no puede usar:

#! /usr/bin/env sh -
#! /usr/bin/perl -w --

Como en la mayoría de los sistemas, el mecanismo shebang solo permite un argumento después de la ruta del intérprete.

En cuanto a si usar #! /bin/sh -vs #!/bin/sh -, eso es solo cuestión de gustos. Prefiero el primero ya que hace que la ruta del intérprete sea más visible y facilita la selección del mouse. Hay una leyenda que dice que el espacio era necesario en algunas versiones antiguas de Unix, pero AFAIK, que nunca se ha verificado.

Puede encontrar una muy buena referencia sobre el shebang de Unix en https://www.in-ulm.de/~mascheck/various/shebang

Stéphane Chazelas
fuente
1
¿Tiene esto alguna relevancia para #! / Bin / bash?
Joe
1
@ Joe, sí, por supuesto, bashacepta opciones como cualquier otro shell. Al ser un shell Bourne-like y POSIX, bashacepta ambos #! /bin/bash -y #! /bin/bash --.
Stéphane Chazelas