Por lo general trabajo con los servidores de Ubuntu LTS, que por lo que entiendo enlace simbólico /bin/sh
a /bin/dash
. Muchas otras distribuciones aunque enlace simbólico /bin/sh
a /bin/bash
.
¿De eso entiendo que si un script usa #!/bin/sh
en la parte superior, puede no ejecutarse de la misma manera en todos los servidores?
¿Existe una práctica sugerida sobre qué shell usar para los scripts cuando se desea la máxima portabilidad de esos scripts entre servidores?
#!/bin/sh
y no use nada más que lo que proporcionó el shell original.Respuestas:
Hay aproximadamente cuatro niveles de portabilidad para los scripts de shell (en lo que respecta a la línea shebang):
Más portátil: use un
#!/bin/sh
shebang y use solo la sintaxis de shell básica especificada en el estándar POSIX . Esto debería funcionar en casi cualquier sistema POSIX / unix / linux. (Bueno, excepto Solaris 10 y versiones anteriores que tenían el shell Bourne heredado real, anterior a POSIX, por lo que no es compatible/bin/sh
).La segunda más portátil: use una
#!/bin/bash
(o#!/usr/bin/env bash
) línea shebang y manténgase en las funciones de bash v3. Esto funcionará en cualquier sistema que tenga bash (en la ubicación esperada).Tercero más portátil: use una
#!/bin/bash
(o#!/usr/bin/env bash
) línea shebang y use las funciones de bash v4. Esto fallará en cualquier sistema que tenga bash v3 (por ejemplo, macOS, que tiene que usarlo por razones de licencia).Menos portátil: use un
#!/bin/sh
shebang y use extensiones bash para la sintaxis de shell POSIX. Esto fallará en cualquier sistema que tenga algo más que bash para / bin / sh (como las versiones recientes de Ubuntu). Nunca hagas esto; No es solo un problema de compatibilidad, es simplemente incorrecto. Desafortunadamente, es un error que mucha gente comete.Mi recomendación: use el más conservador de los primeros tres que proporciona todas las funciones de shell que necesita para el script. Para una portabilidad máxima, use la opción n. ° 1, pero según mi experiencia, algunas funciones bash (como las matrices) son lo suficientemente útiles como para ir con la n. ° 2.
Lo peor que puedes hacer es # 4, usar el shebang incorrecto. Si no está seguro de qué características son POSIX básicas y cuáles son las extensiones bash, quédese con un bash shebang (es decir, opción # 2), o pruebe el script a fondo con un shell muy básico (como el tablero en sus servidores Ubuntu LTS). El wiki de Ubuntu tiene una buena lista de bashisms a tener en cuenta .
Hay una muy buena información sobre el historial y las diferencias entre los shells en la pregunta de Unix y Linux "¿Qué significa ser compatible con sh?" y la pregunta de Stackoverflow "Diferencia entre sh y bash" .
Además, tenga en cuenta que el shell no es lo único que difiere entre los diferentes sistemas; si está acostumbrado a Linux, está acostumbrado a los comandos GNU, que tienen muchas extensiones no estándar que puede no encontrar en otros sistemas Unix (por ejemplo, bsd, macOS). Desafortunadamente, no hay una regla simple aquí, solo tiene que conocer el rango de variación para los comandos que está utilizando.
Uno de los comandos más desagradables en términos de portabilidad es uno de los más básicos:
echo
. Cada vez que lo use con alguna opción (por ejemplo,echo -n
oecho -e
), o con cualquier escape (barra invertida) en la cadena para imprimir, las diferentes versiones harán diferentes cosas. Cada vez que desee imprimir una cadena sin un salto de línea después de ella, o con escapes en la cadena, useprintf
en su lugar (y aprenda cómo funciona, es más complicado de lo queecho
es). Elps
comando también es un desastre .Otra cosa que hay que observar en general son las extensiones recientes / GNUish para la sintaxis de la opción de comando: el formato de comando antiguo (estándar) es que el comando va seguido de opciones (con un solo guión, y cada opción es una sola letra), seguido de argumentos de comando. Las variantes recientes (y a menudo no portátiles) incluyen opciones largas (generalmente introducidas con
--
), que permiten que las opciones aparezcan después de los argumentos y que se usan--
para separar las opciones de los argumentos.fuente
sh
), por/bin/sh
lo que no se garantiza que sea la ruta correcta. Lo más portátil es no especificar ningún shebang o adaptar el shebang al sistema operativo utilizado.#!/bin/sh
a#!/bin/bash
pesar de que, el guión funcionó a la perfección. (Para mi defensa, que el guión había evolucionado con el tiempo a partir de uno que realmente sólo necesita sh-ismos, a uno que se basó en el comportamiento fiesta similar.)ksh
comportamiento, no de Bourne. Lo que hacen la mayoría de las distribuciones de Linux después del FHS es tener/bin/sh
un enlace simbólico con el shell que seleccionan para proporcionar compatibilidad POSIX, generalmentebash
odash
.En el
./configure
script que prepara el lenguaje TXR para construir, escribí el siguiente prólogo para una mejor portabilidad. El script se iniciará incluso si se#!/bin/sh
trata de un Bourne Shell antiguo no compatible con POSIX. (Construyo cada versión en una VM Solaris 10).La idea aquí es que encontremos un shell mejor que el que estamos ejecutando, y volver a ejecutar el script usando ese shell. Se establece
txr_shell
la variable de entorno, de modo que el script ejecutado nuevamente sepa que es la instancia recursiva ejecutada nuevamente.(En mi secuencia de comandos, la
txr_shell
variable también se usa posteriormente, para exactamente dos propósitos: en primer lugar, se imprime como parte de un mensaje informativo en la salida de la secuencia de comandos. En segundo lugar, se instala como laSHELL
variable en elMakefile
, por lo quemake
utilizará esto shell también para ejecutar recetas).En un sistema donde
/bin/sh
está el guión, puede ver que la lógica anterior encontrará/bin/bash
y volverá a ejecutar el script con eso.En un cuadro de Solaris 10, se
/usr/xpg4/bin/sh
activará si no se encuentra Bash.El prólogo está escrito en un dialecto de shell conservador, utilizando
test
para pruebas de existencia de archivos, y el${@+"$@"}
truco para expandir argumentos que atienden a algunos shells viejos rotos (que simplemente sería"$@"
si estuviéramos en un shell conforme POSIX).fuente
x
piratería si se usaran las citas adecuadas, ya que las situaciones que exigían ese modismo rodean las invocaciones de pruebas ahora obsoletas-a
o la-o
combinación de múltiples pruebas.test x$whatever
que estoy perpetrando allí parece una cebolla en el barniz. Si no podemos confiar en el viejo shell roto para hacer citas, entonces el${@+"$@"}
intento final no tiene sentido.Todas las variaciones del lenguaje shell Bourne son objetivamente terribles en comparación con los lenguajes de script modernos como Perl, Python, Ruby, node.js e incluso (posiblemente) Tcl. Si tiene que hacer algo incluso un poco complicado, será más feliz a largo plazo si usa uno de los anteriores en lugar de un script de shell.
La única ventaja que aún tiene el lenguaje shell sobre esos idiomas más nuevos es que se garantiza que algo que se llama a sí mismo
/bin/sh
existe en todo lo que pretende ser Unix. Sin embargo, ese algo puede no ser compatible con POSIX; muchos de los Unixes propietarios heredados congelaron el lenguaje implementado/bin/sh
y las utilidades en la RUTA predeterminada antes de los cambios exigidos por Unix95 (sí, Unix95, hace veinte años y contando). Puede haber un conjunto de Unix95, o incluso POSIX.1-2001 si tiene suerte, herramientas en un directorio que no está en la RUTA predeterminada (por ejemplo/usr/xpg4/bin
), pero no se garantiza que existan.Sin embargo, los conceptos básicos de Perl tienen más probabilidades de estar presentes en una instalación de Unix seleccionada arbitrariamente que Bash. (Por "lo básico de Perl" quiero decir que
/usr/bin/perl
existe y es una versión, posiblemente bastante antigua, de Perl 5, y si tiene suerte, el conjunto de módulos que se incluyen con esa versión del intérprete también están disponibles).Por lo tanto:
Si está escribiendo algo que tiene que funcionar en todas partes que pretende ser Unix (como un script de "configuración"), debe usarlo
#! /bin/sh
y no debe usar ninguna extensión. Hoy en día escribiría shell compatible con POSIX.1-2001 en esta circunstancia, pero estaría preparado para parchear POSIXisms si alguien solicitara soporte para hierro oxidado.Pero si usted está no escribiendo algo que tiene que funcionar en todas partes, entonces el momento en que se ven tentados a utilizar cualquiera del bash en absoluto, usted debe parar y volver a escribir toda la cosa en un mejor lenguaje de script en su lugar. Tu futuro yo te lo agradecerá.
(Entonces, ¿cuándo es apropiado usar extensiones de Bash? Para primer orden: nunca. Para segundo orden: solo para extender el entorno interactivo de Bash, por ejemplo, para proporcionar complementos de pestañas inteligentes y avisos sofisticados).
fuente
find ... -print0 |while IFS="" read -rd "" file; do ...; done |tar c --null -T -
. No sería posible hacerlo funcionar correctamente sin bashisms (read -d ""
) y extensiones GNU (find -print0
,tar --null
). Reimplementar esa línea en Perl sería mucho más largo y torpe. Este es el tipo de situación en la que lo correcto es usar bashismos y otras extensiones que no sean POSIX.perl -MFile::Find -e 'our @tarcmd = ("tar", "c"); find(sub { return unless ...; ...; push @tarcmd, $File::Find::name }, "."); exec @tarcmd
Tenga en cuenta que no solo no necesita ningún bashism, no necesita ninguna extensión tar de GNU. (Si la longitud de la línea de comando es preocupante o si desea que el escaneo y el alquitrán se ejecuten en paralelo, entonces lo necesitatar --null -T
).find
devolví más de 50,000 nombres de archivo en mi escenario, por lo que es probable que su script alcance el límite de longitud del argumento. Una implementación correcta que no use extensiones que no sean POSIX tendría que construir la salida tar de forma incremental, o tal vez usar Archive :: Tar :: Stream en el código Perl. Por supuesto, se puede hacer, pero en este caso el script Bash es mucho más rápido de escribir y tiene menos repetitivo, y por lo tanto, menos espacio para errores.find ... -print0 |perl -0ne '... print ...' |tar c --null -T -
. Pero las preguntas son: 1) Estoy usandofind -print0
y detar --null
todos modos, entonces, ¿cuál es el punto de evitar el bashismread -d ""
, que es exactamente el mismo tipo de cosa (una extensión GNU para manejar el separador nulo que, a diferencia de Perl, algún día puede convertirse en algo POSIX) )? 2) ¿Perl está realmente más extendido que Bash? He estado usando imágenes de Docker recientemente, y muchas de ellas tienen Bash pero no Perl. Es más probable que se ejecuten nuevos scripts allí que en los antiguos Unices.