Múltiples argumentos en shebang


33

Me pregunto si hay una forma general de pasar múltiples opciones a un ejecutable a través de la línea shebang ( #!).

Yo uso NixOS, y la primera parte del shebang en cualquier script que escribo suele ser /usr/bin/env. El problema que encuentro es que todo lo que viene después es interpretado como un único archivo o directorio por el sistema.

Supongamos, por ejemplo, que quiero escribir un script para que se ejecute bashen modo posix. La forma ingenua de escribir el shebang sería:

#!/usr/bin/env bash --posix

pero intentar ejecutar el script resultante produce el siguiente error:

/usr/bin/env: ‘bash --posix’: No such file or directory

Soy consciente de esta publicación , pero me preguntaba si había una solución más general y más limpia.


EDITAR : Sé que para los guiones Guile , hay una manera de lograr lo que quiero, documentado en la Sección 4.3.4 del manual:

 #!/usr/bin/env sh
 exec guile -l fact -e '(@ (fac) main)' -s "$0" "$@"
 !#

El truco, aquí, es que la segunda línea (comenzando con exec) se interpreta como código shpero, al estar en el bloque #!... !#, como un comentario, y por lo tanto ignorada, por el intérprete Guile.

¿No sería posible generalizar este método a cualquier intérprete?


Segunda edición : después de jugar un poco, parece que, para los intérpretes que pueden leer sus comentarios stdin, el siguiente método funcionaría:

#!/usr/bin/env sh
sed '1,2d' "$0" | bash --verbose --posix /dev/stdin; exit;

Sin embargo, probablemente no sea óptimo, ya que el shproceso dura hasta que el intérprete haya terminado su trabajo. Cualquier comentario o sugerencia sería apreciada.



Respuestas:


27

No existe una solución general, al menos no si necesita admitir Linux, porque el núcleo de Linux trata todo lo que sigue a la primera "palabra" en la línea shebang como un argumento único .

No estoy seguro de cuáles son las restricciones de NixOS, pero normalmente solo escribiría su shebang como

#!/bin/bash --posix

o, cuando sea posible, configure las opciones en el script :

set -o posix

Alternativamente, puede hacer que el script se reinicie con la invocación de shell apropiada:

#!/bin/sh -

if [ "$1" != "--really" ]; then exec bash --posix -- "$0" --really "$@"; fi

shift

# Processing continues

Este enfoque se puede generalizar a otros idiomas, siempre y cuando encuentre una manera de ignorar el primer par de líneas (que son interpretadas por el shell).

GNU coreutils' envproporciona una solución alternativa desde la versión 8.30, consulte la respuesta de Unode para más detalles. (Esto está disponible en Debian 10 y posterior, RHEL 8 y posterior, Ubuntu 19.04 y posterior, etc.)


18

Aunque no es exactamente portátil, a partir de coreutils 8.30 y de acuerdo con su documentación , podrá utilizar:

#!/usr/bin/env -S command arg1 arg2 ...

Así dado:

$ cat test.sh
#!/usr/bin/env -S showargs here 'is another' long arg -e "this and that " too

conseguirás:

% ./test.sh 
$0 is '/usr/local/bin/showargs'
$1 is 'here'
$2 is 'is another'
$3 is 'long'
$4 is 'arg'
$5 is '-e'
$6 is 'this and that '
$7 is 'too'
$8 is './test.sh'

y en caso de que tengas curiosidad showargses:

#!/usr/bin/env sh
echo "\$0 is '$0'"

i=1
for arg in "$@"; do
    echo "\$$i is '$arg'"
    i=$((i+1))
done

Es muy bueno saberlo para futuras referencias.
John McGehee

Esa opción se copió de FreeBSD envdonde -Sse agregó en 2005. Ver lists.gnu.org/r/coreutils/2018-04/msg00011.html
Stéphane Chazelas

Funciona en Fedora 29
Eric

@unode algunas mejoras de showargs: pastebin.com/q9m6xr8H y pastebin.com/gS8AQ5WA (one-liner)
Eric

FYI: a partir de coreutils 8.31, envincluye el suyo showargs: la opción -v, por ejemplo#!/usr/bin/env -vS --option1 --option2 ...
chocolateboy

9

El estándar POSIX es muy breve al describir #! :

De la sección de justificación de la documentación de la exec()familia de interfaces del sistema :

Otra forma en que algunas implementaciones históricas manejan los scripts de shell es reconociendo los primeros dos bytes del archivo como la cadena de caracteres #!y usando el resto de la primera línea del archivo como el nombre del intérprete de comandos para ejecutar.

Desde el sección de Introducción de Shell :

La cáscara lee su entrada de un archivo (Ver sh), de la -copción o de la system()y popen()funciones definidas en el volumen del sistema Interfaces de POSIX.1-2008.Si la primera línea de un archivo de comandos de shell comienza con los caracteres #!, los resultados no se especifican .

Esto básicamente significa que cualquier implementación (el Unix que está utilizando) es libre de hacer los detalles del análisis de la línea shebang como lo desee.

Algunos Unices, como macOS (no puede probar ATM), dividirán los argumentos dados al intérprete en la línea shebang en argumentos separados, mientras que Linux y la mayoría de los otros Unices darán los argumentos como una sola opción para el intérprete.

Por lo tanto, no es prudente confiar en que la línea shebang pueda tomar más de un argumento.

Vea también la sección de Portabilidad del artículo de Shebang en Wikipedia .


Una solución fácil, que se puede generalizar a cualquier utilidad o lenguaje, es crear un script de envoltura que ejecute el script real con los argumentos de línea de comando apropiados:

#!/bin/sh
exec /bin/bash --posix /some/path/realscript "$@"

No creo que intente personalmente hacer que se vuelva a ejecutar , ya que eso se siente algo frágil.


7

El shebang se describe en execve(2) la página man de la siguiente manera:

#! interpreter [optional-arg]

Se aceptan dos espacios en esta sintaxis:

  1. Un espacio antes del intérprete ruta , pero este espacio es opcional.
  2. Un espacio que separa la ruta del intérprete y su argumento opcional.

Tenga en cuenta que no utilicé el plural al hablar de un argumento opcional, ni la sintaxis anterior usa [optional-arg ...], ya que puede proporcionar a lo sumo un solo argumento .

En lo que respecta a las secuencias de comandos de shell, puede utilizar el set comando incorporado cerca del comienzo de su secuencia de comandos que le permitirá establecer los parámetros de los intérpretes, proporcionando el mismo resultado que si usara argumentos de línea de comandos.

En tu caso:

set -o posix

Desde el indicador Bash, verifique la salida de help setpara obtener todas las opciones disponibles.


1
Se le permite tener más de dos espacios, solo se consideran parte del argumento opcional.
Stephen Kitt

@StephenKitt: De hecho, el espacio en blanco aquí debe tomarse más como una categoría que el espacio real. Supongo que otros espacios en blanco, como las pestañas, también deberían ser ampliamente aceptados.
WhiteWinterWolf

3

En Linux, el shebang no es muy flexible; de acuerdo con las respuestas múltiples (la respuesta de Stephen Kitt y la de Jörg W Mittag ), no hay una forma designada para pasar múltiples argumentos en una línea shebang.

No estoy seguro de si será de utilidad para alguien, pero he escrito un breve guión para implementar la función que falta. Ver https://gist.github.com/loxaxs/7cbe84aed1c38cf18f70d8427bed1efa .

También es posible escribir soluciones alternativas integradas. A continuación, presento cuatro soluciones independientes del lenguaje aplicadas al mismo script de prueba y el resultado que imprime cada una. Supongo que el script es ejecutable y está en /tmp/shebang.


Envolviendo su script en un bash heredoc dentro de la sustitución del proceso

Hasta donde yo sé, esta es la forma más confiable de hacer un lenguaje independiente. Permite pasar argumentos y preserva stdin. El inconveniente es que el intérprete no conoce la ubicación (real) del archivo que lee.

#!/bin/bash
exec python3 -O <(cat << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv
try:
    print("input() 0 ::", input())
    print("input() 1 ::", input())
except EOFError:
    print("input() caused EOFError")
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")
EOWRAPPER
) "$@"

echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'Impresiones de llamadas :

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /dev/fd/62
argv[1:]  :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: False
PYTHON_SCRIPT_END

Tenga en cuenta que la sustitución del proceso produce un archivo especial. Esto puede no adaptarse a todos los ejecutables. Por ejemplo, se #!/usr/bin/lessqueja:/dev/fd/63 is not a regular file (use -f to see it)

No sé si es posible tener heredoc dentro de la sustitución del proceso en el tablero.


Envolviendo su script en un simple heredoc

Más corto y simple, pero no podrá acceder stdindesde su script y requiere que el intérprete pueda leer y ejecutar un script stdin.

#!/bin/sh
exec python3 - "$@" << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

try:
    print("input() 0 ::", input())
    print("input() 1 ::", input())
except EOFError:
    print("input() caused EOFError")
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")
EOWRAPPER

echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'Impresiones de llamadas :

PYTHON_SCRIPT_BEGINNING
input() caused EOFError
argv[0]   :: -
argv[1:]  :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: True
PYTHON_SCRIPT_END

Usa una system()llamada awk pero sin argumentos

Pasa correctamente el nombre del archivo ejecutado, pero su script no recibirá los argumentos que le dé. Tenga en cuenta que awk es el único idioma que conozco cuyo intérprete está instalado en Linux de forma predeterminada y lee sus instrucciones desde la línea de comandos de forma predeterminada.

#!/usr/bin/gawk BEGIN {system("python3 -O " ARGV[1])}
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")

echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'Impresiones de llamadas :

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /tmp/shebang
argv[1:]  :: []
__debug__ :: False
PYTHON_SCRIPT_END

Use awk 4.1+ system() llamada , siempre que sus argumentos no contengan espacios

Agradable, pero solo si está seguro de que su script no se llamará con argumentos que contengan espacios. Como puede ver, sus argumentos que contienen espacios se dividirían, a menos que se escapen los espacios.

#!/usr/bin/gawk @include "join"; BEGIN {system("python3 -O " join(ARGV, 1, ARGC, " "))}
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")

echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'Impresiones de llamadas :

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /tmp/shebang
argv[1:]  :: ['arg1', 'arg2', 'contains', 'spaces', 'arg3 uses \\escapes\\']
__debug__ :: False
PYTHON_SCRIPT_END

Para las versiones awk inferiores a 4.1, deberá utilizar la concatenación de cadenas dentro de un bucle for, consulte la función de ejemplo https://www.gnu.org/software/gawk/manual/html_node/Join-Function.html .


1
Cite el terminador del documento aquí para inhibir $variableo `command`sustituir:exec python3 -O <(cat <<'EOWRAPPER'
John McGehee

2

Un truco para usar LD_LIBRARY_PATHcon python en la línea #!(shebang) que no depende de nada más que el shell y funciona como un regalo:

#!/bin/sh
'''' 2>/dev/null; exec /usr/bin/env LD_LIBRARY_PATH=. python -x "$0" "$@" #'''

__doc__ = 'A great module docstring'

Como se explica en otra parte de esta página, algunos shells como sh pueden tomar un script en su entrada estándar.

El script que damos shintenta ejecutar el comando ''''que se simplifica a ''(la cadena vacía) shy, por supuesto, no puede ejecutarlo ya que no hay un ''comando, por lo que normalmente se muestra line 2: command not founden el descriptor de error estándar, pero redirigimos este mensaje 2>/dev/nullal agujero negro más cercano porque sería desordenado y confuso para el usuario dejarlo shmostrar.

Luego procedemos al comando que nos interesa: execque reemplaza el proceso actual de shell por lo que sigue, en nuestro caso: /usr/bin/env pythoncon los parámetros adecuados:

  • "$0" para que Python sepa qué script debe abrir e interpretar, y también establecer sys.argv[0]
  • "$@"para configurar python sys.argv[1:]a los argumentos pasados ​​en la línea de comandos del script.

Y también pedimos envestablecer la LD_LIBRARY_PATHvariable de entorno, que es el único punto del hack.

El comando de shell termina en el comentario que comienza #para que el shell ignore las comillas triples finales '''.

shluego se reemplaza por una nueva instancia brillante del intérprete de python que abre y lee el script de origen de python dado como primer argumento (el "$0").

Python abre el archivo y salta la primera línea de la fuente gracias al -xargumento. Nota: también funciona sin -xporque para Python un shebang es solo un comentario .

Python luego interpreta la segunda línea como la cadena de documentación para el archivo de módulo actual, por lo que si necesita una cadena de documentación de módulo válida, simplemente configure lo __doc__primero en su programa de Python como en el ejemplo anterior.



Dado que una cadena vacía está ... um ... vacía, debería ser capaz de soltar su comando mono no encontrado: ''''exec ...debería hacer el trabajo. Tenga en cuenta que no hay espacio antes de exec o lo hará buscar el comando vacío. Desea empalmar el vacío en el primer argumento para que así $0sea exec.
Caleb

1

Encontré una solución bastante estúpida al buscar un ejecutable que exceptúa un script como argumento único:

#!/usr/bin/awk BEGIN{system("bash --posix "ARGV[1])}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.