¿Cómo funciona este shebang que comienza con un guión doble (-)?
14
He encontrado el siguiente tipo de shebang en la página de RosettaCode:
--(){:;}; exec db2 -txf "$0"
Funciona para Db2, y algo similar para Postgres. Sin embargo, no entiendo toda la línea.
Sé que el guión doble es un comentario en SQL, y después de eso llama al ejecutable Db2 con algunos parámetros que pasan el archivo como archivo. Pero, ¿qué pasa con el paréntesis, los corchetes, el colon y el punto y coma, y cómo puede reemplazar un verdadero shebang #! ?
El script no tiene shebang / hashbang / #!line, simplemente porque no tiene un doble guión #!.
Sin embargo, el script será ejecutado por un shell (ver preguntas y respuestas vinculadas anteriormente), y en ese shell, si -es un carácter válido en el nombre de una función, la línea declara una función de shell llamada --que no hace nada (bueno, se ejecuta :, que no hace nada ) y que nunca se llama.
La función, en la notación de líneas múltiples más común (solo para que sea más obvio cómo se ve, ya que su nombre extraño oscurece el hecho de que en realidad es una función):
--(){:}
El único propósito de la definición de la función es tener una línea que sea válida en un script de shell y al mismo tiempo un comando SQL válido (un comentario). Este tipo de código se llama políglota .
Después de declarar la función de shell falsa, el script, cuando es ejecutado por un intérprete de script de shell, se usa execpara reemplazar el shell actual con el proceso resultante de la ejecución db2 -txf "$0", que sería lo mismo que usar db2 -txfen la ruta de acceso del script desde la línea de comandos.
Este truco probablemente no funcionaría de manera confiable en sistemas donde dashu otros ashshells basados yash, el shell Bourne, ksh88o ksh93se usan como /bin/sh, ya que estos shell no aceptan funciones cuyo nombre contenga guiones.
¡Oh si! Y gracias por recordarme cómo se llama ese tipo de cosas. :)
ilkkachu
6
Como ya dijo @Kusalananda, ese truco está roto y no funcionará en todos los proyectiles.
Aquí está mi opinión para hacerlo de forma portátil:
--/..2>/dev/null; exec db2 -txf "$0"
El primer comando debería fallar incluso si --existe un archivo / directorio nombrado en el directorio actual y cualquier error será cerrado por el 2>/dev/null; el shell continuará con el segundo comando, el exec.
Todavía no es realmente portátil. No es un script válido y todavía confía en el shell de llamada para evitar el hecho de que el núcleo se negará a ejecutar el script y volverá ENOEXECsi lo intenta. Intenta ejecutar el script debajo stracepara ver a qué me refiero.
kasperd
@kasperd, aún debe ser portátil, se supone que el shell debe ejecutar el script como un script de shell si exec()no funciona en él. "Si la función execl () falla debido a un error equivalente al error [ENOEXEC], el shell ejecutará un comando equivalente a tener un shell invocado con el nombre del comando como su primer operando, ..." (ver pubs.opengroup .org / onlinepubs / 9699919799.2018edition / utilities / ... )
ilkkachu
@ilkkachu Pero los scripts no siempre se ejecutan desde un shell. Si intenta usar el script en cualquier otro contexto donde un ejecutable funcionaría, fallará. Además, los shells no están de acuerdo con qué intérprete usar. Por lo tanto, su script ahora se comportará de manera diferente o fallará por completo según el contexto desde el que se llama.
kasperd
@kasperd, bueno, claro, no funcionará si lo haces exec()directamente desde otra cosa que no sea un shell. Pero, ¿cuál sería ese caso? Es posible que desee ejecutar el script crono algo similar, pero creo que de todos modos ejecuta todo a través de un shell, e incluso si no, es fácil de explicar db2 -txf /path/to/scripten ese caso, ya que solo necesita hacerlo una vez. Tener el trabajo abreviado es sobre todo útil en un shell interactivo. Pero claro, un script de contenedor separado podría ser más robusto.
ilkkachu
1
@kasperd No te molestaré con documentos y estándares; ¡solo inténtalo! echo 'int main(int c,char**a){execvp(a[1],a+1);}' | cc -include unistd.h -xc -; echo echo yeah > a.sh; chmod 755 a.sh; ./a.out ./a.sh; PATH=`pwd` ./a.out a.sh
Como ya dijo @Kusalananda, ese truco está roto y no funcionará en todos los proyectiles.
Aquí está mi opinión para hacerlo de forma portátil:
El primer comando debería fallar incluso si
--
existe un archivo / directorio nombrado en el directorio actual y cualquier error será cerrado por el2>/dev/null
; el shell continuará con el segundo comando, elexec
.fuente
ENOEXEC
si lo intenta. Intenta ejecutar el script debajostrace
para ver a qué me refiero.exec()
no funciona en él. "Si la función execl () falla debido a un error equivalente al error [ENOEXEC], el shell ejecutará un comando equivalente a tener un shell invocado con el nombre del comando como su primer operando, ..." (ver pubs.opengroup .org / onlinepubs / 9699919799.2018edition / utilities / ... )exec()
directamente desde otra cosa que no sea un shell. Pero, ¿cuál sería ese caso? Es posible que desee ejecutar el scriptcron
o algo similar, pero creo que de todos modos ejecuta todo a través de un shell, e incluso si no, es fácil de explicardb2 -txf /path/to/script
en ese caso, ya que solo necesita hacerlo una vez. Tener el trabajo abreviado es sobre todo útil en un shell interactivo. Pero claro, un script de contenedor separado podría ser más robusto.echo 'int main(int c,char**a){execvp(a[1],a+1);}' | cc -include unistd.h -xc -; echo echo yeah > a.sh; chmod 755 a.sh; ./a.out ./a.sh; PATH=`pwd` ./a.out a.sh