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.sh
siempre use el Zsh
disponible en PATH
?
env
que no está en / bin y / usr / bin? Intentawhich -a env
confirmar.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
zsh
característica única y si la prueba resulta falsa, tenga el scriptexec
conzsh
, donde la prueba tendrá éxito y simplemente continúe.Una característica única en
zsh
, por ejemplo, es la presencia de la$ZSH_VERSION
variable: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/sh
Bourne o POSIX, pero nuestra sintaxis es compatible con ambos). Si no$ZSH_VERSION
está configurado, el script se procesa . Si está configurado (resp. El script ya se está ejecutandoexec
zsh
$ZSH_VERSION
zsh
), la prueba simplemente se omite. Voilà.Esto solo falla si
zsh
no está$PATH
en absoluto.Editar: Para asegurarse de que, sólo
exec
unazsh
en los lugares habituales, se podría utilizar algo comoEsto podría salvarlo accidentalmente
exec
de algo$PATH
que no es lozsh
que está esperando.fuente
zsh
en la$PATH
que no es el que usted espera.zsh
binario en las ubicaciones estándar es realmente unzsh
.zsh
dó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
.zsh
binario desde$PATH
para obtener la ruta delzsh
binario 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
### BEGIN
usandosed
.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óexec
para 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 -s
es comouname
(uname es para nombre Unix). Olvidó mencionar que la opción activa la omisión .-x
perl
NOTA: @ 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 sh
referencia 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
zsh
el 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_document
o 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
$expansion
en ninguno de sus comandos simples constituyentes hasta que ejecuta individualmente cada uno.Entonces, en el siguiente ejemplo, las
;semicolon
palabras reservadas delimitan comandos simples individuales mientras que el carácter no escapado\newline
delimita 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$expansions
en 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-DOCUMENT
ES 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)sh
el shell crea un archivo normal/tmp
, descarga la salida, la asigna a un descriptor, luego elimina el/tmp
archivo para que la copia del descriptor del núcleo sea todo lo que queda.dash
evita todas esas tonterías y simplemente deja caer su procesamiento de salida en un|pipe
archivo anónimo dirigido al<<
objetivo de redireccionamiento .Esto hace que
dash
:funcionalmente equivalente a
bash
's:mientras que
dash
la 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
cat
el contenido de cualquier archivo de la cáscara creado porFILE
dentro./file
, hacerlo ejecutable, a continuación, ejecutarlo.El núcleo interpreta las
#!
llamadas y/usr/bin/sh
con un<read
descriptor de archivo asignado./file
.sh
asigna una cadena a la memoria que consiste en el comando compuesto que comienza en_fn()
y termina enSCRIPT
.Cuando
_fn
se llama,sh
primero debe interpretar a continuación, asignar a un descriptor de archivo definido en<<SCRIPT...SCRIPT
antes de invocar_fn
como un especial utilidad integrada porqueSCRIPT
es_fn
's<input.
La salida de cadenas por
printf
ycommand
se escriben en_fn
's estándar de salida>&1
- que es redirigido a la shell actual deARGV0
- o$0
.cat
concatena su descriptor de archivo de<&0
entrada estándarSCRIPT
- sobre el argumento>
del shell actual truncadoARGV0
, o$0
.Completando su comando compuesto actual ya leído , se encuentra
sh exec
el$0
argumento ejecutable y recientemente reescrito .Desde el momento en que
./file
se llama hasta que sus instrucciones contenidas especifiquen que debe volverseexec
ash
leer , lo lee en un solo comando compuesto a la vez mientras los ejecuta, mientras que en./file
sí 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
git
sincronizació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 -v
es 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
exec
comando 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 command
una opinión contradictoria.man command
una 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 lasman
páginas que estaba mirando el otro día.command -v
ejemplo 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/sh
no 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/sh
punto es bueno, pero en ese caso, ¿necesitas una premodificación#!
? ¿Y no esawk
tan probable que sea tan falso como lozsh
es?sed -i
modos es todo lo que hace GNU . Personalmente, creo que el$PATH
problema 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, ahoragetconf
podría ser falso, pero las posibilidades son casi nulas, lo mismo que para elloszsh
yawk.
$(getconf PATH)
No es Bourne.cp $0 $0.old
es la sintaxis zsh. El equivalente de Bourne seríacp "$0" "$0.old"
lo que quisierascp -- "$0" "$0.old"