Tengo un script que quiero poder ejecutar en dos máquinas. Estas dos máquinas obtienen copias del script del mismo repositorio git. El script debe ejecutarse con el intérprete correcto (p. Ej.zsh ).
Desafortunadamente, ambos env yzsh vivir en diferentes lugares en los equipos locales y remotos:
Máquina remota
$ which env
/bin/env
$ which zsh
/some/long/path/to/the/right/zsh
Máquina local
$ which env
/usr/bin/env
$which zsh
/usr/local/bin/zsh
¿Cómo puedo configurar el shebang para que ejecutar el script como /path/to/script.shsiempre use el Zshdisponible en PATH?

envque no está en / bin y / usr / bin? Intentawhich -a envconfirmar.Respuestas:
No puede resolver esto a través de shebang directamente, ya que shebang es puramente estático. Lo que podría hacer es tener algún »multiplicador menos común« (desde una perspectiva de shell) en el shebang y volver a ejecutar su script con el shell correcto, si este LCM no es zsh. En otras palabras: haga que su script sea ejecutado por un shell que se encuentra en todos los sistemas, pruebe una
zshcaracterística única y si la prueba resulta falsa, tenga el scriptexecconzsh, donde la prueba tendrá éxito y simplemente continúe.Una característica única en
zsh, por ejemplo, es la presencia de la$ZSH_VERSIONvariable:En este caso simple, el script es ejecutado primero por
/bin/sh(todos los sistemas similares a Unix posteriores a los 80 entienden#!y tienen un/bin/shBourne o POSIX, pero nuestra sintaxis es compatible con ambos). Si no$ZSH_VERSIONestá configurado, el script se procesa . Si está configurado (resp. El script ya se está ejecutandoexeczsh$ZSH_VERSIONzsh), la prueba simplemente se omite. Voilà.Esto solo falla si
zshno está$PATHen absoluto.Editar: Para asegurarse de que, sólo
execunazshen los lugares habituales, se podría utilizar algo comoEsto podría salvarlo accidentalmente
execde algo$PATHque no es lozshque está esperando.fuente
zshen la$PATHque no es el que usted espera.zshbinario en las ubicaciones estándar es realmente unzsh.zshdónde estázsh -c 'whence zsh'. Más simplemente puedes simplementecommand -v zsh. Vea mi respuesta para saber cómo direccionar dinámicamente el#!bang.zshbinario desde$PATHpara obtener la ruta delzshbinario no resolvería el problema que señaló @RyanReich, ¿verdad? :)zsh, no, supongo que no. Pero si inserta la cadena resultante en su hash bang y luego ejecuta su propio script, al menos sabrá lo que está obteniendo. Aún así, sería una prueba más simple que hacer un bucle.Durante años he usado algo similar para tratar con las diversas ubicaciones de Bash en los sistemas que necesitaba para ejecutar mis scripts.
Bash / Zsh / etc.
Lo anterior se puede adaptar fácilmente para una variedad de intérpretes. La pieza clave es que este script inicialmente se ejecuta como shell Bourne. Luego se llama recursivamente por segunda vez, pero analiza todo por encima del comentario
### BEGINusandosed.Perl
Aquí hay un truco similar para Perl:
Este método hace uso de la capacidad de Perl cuando se le da un archivo para ejecutar analizará dicho archivo omitiendo todas las líneas que están antes de la línea
#! perl.fuente
$*lugar de"$@", uso inútil de eval, estado de salida no informado (no usóexecpara el primero), mensajes de error faltantes-/--no en stderr, 0 estado de salida para condiciones de error , usando / bin / sh para LIN_BASH, punto y coma inútil (cosmético), usando todo en mayúsculas para las variables que no son env.uname -ses comouname(uname es para nombre Unix). Olvidó mencionar que la opción activa la omisión .-xperlNOTA: @ jw013 hace la siguiente objeción no admitida en los comentarios a continuación:
Respondí sus objeciones de seguridad, señalando que cualquier permisos especiales sólo se requiere una vez por instalación / actualización de la acción con el fin de instalar / actualizar el autoinstalable guión - que personalmente yo llamaría bastante segura. También le señalé una
man shreferencia para lograr objetivos similares por medios similares. En ese momento, no me molesté en señalar que, independientemente de las fallas de seguridad o de otras prácticas generalmente desaconsejadas que puedan o no estar representadas en mi respuesta, es más probable que se basaran en la pregunta en sí misma que en mi respuesta:No satisfecho, @ jw013 continuó objetando promoviendo su argumento aún no respaldado con al menos un par de declaraciones erróneas:
En primer lugar:
EL ÚNICO CÓDIGO EJECUTABLE EN CUALQUIER ESCRITO SHELL EJECUTABLE ES EL
#!MISMO(aunque incluso no
#!está oficialmente especificado )Un script de shell es solo un archivo de texto; para que tenga algún efecto, debe ser leído por otro archivo ejecutable, sus instrucciones luego interpretadas por ese otro archivo ejecutable, antes de que finalmente el otro archivo ejecutable ejecute su interpretación del script de shell No es posible que la ejecución de un archivo de script de shell implique menos de dos archivos. Hay una posible excepción en
zshel compilador propio, pero con esto tengo poca experiencia y de ninguna manera está representado aquí.El hashbang de un script de shell debe apuntar a su intérprete previsto o debe descartarse como irrelevante.
EL COMPORTAMIENTO DE RECONOCIMIENTO / EJECUCIÓN DE SHELL'S TOKEN ESTÁ DEFINIDO POR LAS NORMAS
El shell tiene dos modos básicos de analizar e interpretar su entrada: o su entrada actual está definiendo a
<<here_documento está definiendo a{ ( command |&&|| list ) ; } &- en otras palabras, el shell interpreta un token como un delimitador para un comando que debe ejecutar una vez que lo ha leído en o como instrucciones para crear un archivo y asignarlo a un descriptor de archivo para otro comando. Eso es.Al interpretar comandos para ejecutar el shell delimita tokens en un conjunto de palabras reservadas. Cuando la carcasa se encuentra una abertura de contadores debe continuar a leer en una lista de comandos hasta que la lista es o bien delimitada por un cierre de contadores, tal como un salto de línea - en su caso - o el cierre token de como
})para({antes de la ejecución.El shell distingue entre un comando simple y un comando compuesto. El comando compuesto es el conjunto de comandos que deben leerse antes de la ejecución, pero el shell no se ejecuta
$expansionen ninguno de sus comandos simples constituyentes hasta que ejecuta individualmente cada uno.Entonces, en el siguiente ejemplo, las
;semicolonpalabras reservadas delimitan comandos simples individuales mientras que el carácter no escapado\newlinedelimita entre los dos comandos compuestos:Esa es una simplificación de las pautas. Se vuelve mucho más complicado cuando se consideran los componentes integrados de shell, subshell, entorno actual, etc., pero, para mis propósitos aquí, es suficiente.
Y hablando de listas incorporadas y de comandos, a
function() { declaration ; }es simplemente un medio de asignar un comando compuesto a un comando simple. El shell no debe realizar ninguna$expansionsen la declaración de declaración en sí, para incluir<<redirections>, sino que debe almacenar la definición como una cadena literal única y ejecutarla como un shell especial incorporado cuando se le solicite.Por lo tanto, una función de shell declarada en un script de shell ejecutable se almacena en la memoria del shell de interpretación en su forma de cadena literal, sin expandir para incluir documentos adjuntos aquí como entrada, y se ejecuta independientemente de su archivo fuente cada vez que se llama como un shell incorporado. durante el tiempo que dure el entorno actual del shell.
A
<<HERE-DOCUMENTES UN ARCHIVO EN LÍNEA¿Lo ves? Para cada shell arriba, el shell crea un archivo y lo asigna a un descriptor de archivo. En
zsh, (ba)shel shell crea un archivo normal/tmp, descarga la salida, la asigna a un descriptor, luego elimina el/tmparchivo para que la copia del descriptor del núcleo sea todo lo que queda.dashevita todas esas tonterías y simplemente deja caer su procesamiento de salida en un|pipearchivo anónimo dirigido al<<objetivo de redireccionamiento .Esto hace que
dash:funcionalmente equivalente a
bash's:mientras que
dashla implementación es al menos POSIXly portable.QUE HACE VARIOS ARCHIVOS
Entonces, en la respuesta a continuación cuando lo hago:
Sucede lo siguiente:
La primera vez que
catel contenido de cualquier archivo de la cáscara creado porFILEdentro./file, hacerlo ejecutable, a continuación, ejecutarlo.El núcleo interpreta las
#!llamadas y/usr/bin/shcon un<readdescriptor de archivo asignado./file.shasigna una cadena a la memoria que consiste en el comando compuesto que comienza en_fn()y termina enSCRIPT.Cuando
_fnse llama,shprimero debe interpretar a continuación, asignar a un descriptor de archivo definido en<<SCRIPT...SCRIPTantes de invocar_fncomo un especial utilidad integrada porqueSCRIPTes_fn's<input.La salida de cadenas por
printfycommandse escriben en_fn's estándar de salida>&1- que es redirigido a la shell actual deARGV0- o$0.catconcatena su descriptor de archivo de<&0entrada estándarSCRIPT- sobre el argumento>del shell actual truncadoARGV0, o$0.Completando su comando compuesto actual ya leído , se encuentra
sh execel$0argumento ejecutable y recientemente reescrito .Desde el momento en que
./filese llama hasta que sus instrucciones contenidas especifiquen que debe volverseexecashleer , lo lee en un solo comando compuesto a la vez mientras los ejecuta, mientras que en./filesí mismo no hace nada excepto aceptar felizmente sus nuevos contenidos. Los archivos que están realmente en el trabajo son/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.GRACIAS, DESPUÉS DE TODO
Entonces cuando @ jw013 especifica que:
... en medio de su crítica errónea de esta respuesta, en realidad está perdonando involuntariamente el único método utilizado aquí, que básicamente funciona solo para:
RESPONDER
Todas las respuestas aquí son buenas, pero ninguna de ellas es completamente correcta. Todo el mundo parece afirmar que no puedes seguir tu camino de forma dinámica y permanente
#!bang. Aquí hay una demostración de cómo configurar un camino independiente shebang:MANIFESTACIÓN
SALIDA
¿Lo ves? Simplemente hacemos que el script se sobrescriba. Y solo ocurre una vez después de una
gitsincronización. A partir de ese momento, tiene el camino correcto en la línea #! Bang.Ahora, casi todo eso allí arriba es solo pelusa. Para hacer esto de manera segura necesitas:
Una función definida en la parte superior y llamada en la parte inferior que realiza la escritura. De esta forma, almacenamos todo lo que necesitamos en la memoria y nos aseguramos de que se lea todo el archivo antes de comenzar a escribir sobre él.
Alguna forma de determinar cuál debería ser el camino.
command -ves bastante bueno para esoLos heredocs realmente ayudan porque son archivos reales. Almacenarán su guión mientras tanto. También puedes usar cadenas pero ...
Debe asegurarse de que el shell lea el comando que sobrescribe su script en la misma lista de comandos que el que lo ejecuta.
Mira:
Tenga en cuenta que solo moví el
execcomando una línea hacia abajo. Ahora:No obtengo la segunda mitad de la salida porque el script no puede leer en el siguiente comando. Aún así, porque el único comando que faltaba era el último:
La secuencia de comandos se realizó como debería, principalmente porque estaba todo en el documento heredoc, pero si no lo planifica correctamente, puede truncar su flujo de archivos, que es lo que me sucedió anteriormente.
fuente
man commanduna opinión contradictoria.man commanduna opinión contradictoria - no encontrar una. ¿Me puede dirigir a la sección / párrafo específico del que estaba hablando?man sh- busque 'command -v'. Sabía que estaba en una de lasmanpáginas que estaba mirando el otro día.command -vejemplo del que estabas hablandoman sh. Esa es una secuencia de comandos de instalación de aspecto normal, y no una modificación automática . Incluso los instaladores autónomos solo contienen entradas previas a la modificación y generan sus modificaciones en otro lugar. No se reescriben de la manera que usted recomienda.Aquí hay una forma de tener un script auto modificable que arregle su shebang. Este código debe anteponerse a su script real.
Algunos comentarios:
Debe ser ejecutado una vez por alguien que tenga los derechos para crear y eliminar archivos en el directorio donde se encuentra el script.
Solo utiliza la sintaxis de shell bourne heredada, ya que, a pesar de la creencia popular,
/bin/shno se garantiza que sea un shell POSIX, incluso en sistemas operativos compatibles con POSIX.Establece la RUTA en una que cumpla con POSIX seguida de una lista de posibles ubicaciones zsh para evitar elegir una zsh "falsa".
Si por alguna razón, un script auto modificable no es bienvenido, sería trivial distribuir dos scripts en lugar de uno, el primero es el que desea parchear y el segundo, el que sugerí ligeramente modificado para procesar el primero.
fuente
/bin/shpunto es bueno, pero en ese caso, ¿necesitas una premodificación#!? ¿Y no esawktan probable que sea tan falso como lozshes?sed -imodos es todo lo que hace GNU . Personalmente, creo que el$PATHproblema observado en los comentarios sobre otra respuesta y que aborda de la manera más segura que puedo encontrar aquí en unas pocas líneas se maneja mejor definiendo dependencias de manera simple y explícita y / o pruebas rigurosas y explícitas, por ejemplo, ahoragetconfpodría ser falso, pero las posibilidades son casi nulas, lo mismo que para elloszshyawk.$(getconf PATH)No es Bourne.cp $0 $0.oldes la sintaxis zsh. El equivalente de Bourne seríacp "$0" "$0.old"lo que quisierascp -- "$0" "$0.old"