resueltos - scripts bash ejemplos
¿Script único para ejecutar tanto en lote de Windows como en Linux Bash? (9)
¿Es posible escribir un único archivo de script que se ejecute tanto en Windows (tratado como .bat) como en Linux (a través de Bash)?
Conozco la sintaxis básica de ambos, pero no me di cuenta. Probablemente podría explotar la sintaxis oscura de Bash o algún error en el procesador de lotes de Windows.
El comando para ejecutar puede ser solo una línea para ejecutar otro script.
La motivación es tener solo un comando de inicio de aplicación único para Windows y Linux.
Actualización: la necesidad del script de shell "nativo" del sistema es que necesita elegir la versión del intérprete adecuada, ajustarse a ciertas variables de entorno conocidas, etc. Instalar entornos adicionales como CygWin no es preferible. Me gustaría mantener el concepto ". descargar y ejecutar ".
El único otro lenguaje a considerar para Windows es Windows Scripting Host - WSH, que está predeterminado de forma predeterminada desde 98.
Hay una plataforma independiente de herramientas de compilación como Ant o Maven con sintaxis xml (basada en Java). Entonces, podrías reescribir todos tus scripts en Ant o Maven para ejecutarlos a pesar del tipo de os. O simplemente podría crear una secuencia de comandos Ant wrapper, que analizará el tipo de sistema operativo y ejecutará las secuencias de comandos bat o bash apropiadas.
Hay varias maneras de ejecutar comandos diferentes en bash
y cmd
con el mismo script.
cmd
ignorará las líneas que comienzan con :;
, como se menciona en otras respuestas. Ignorará también la siguiente línea si la línea actual finaliza con el comando rem ^
, ya que el carácter ^
escapará del salto de línea y la siguiente línea se tratará como un comentario de rem
.
En cuanto a hacer que bash
ignore las líneas de cmd
, hay varias formas. He enumerado algunas formas de hacerlo sin romper los comandos cmd
:
Comando #
inexistente (no recomendado)
Si no hay ningún comando #
disponible en cmd
cuando se ejecuta el script, podemos hacer esto:
# 2>nul & echo Hello cmd! & rem ^
echo ''Hello bash!'' #
El carácter #
al principio de la línea de cmd
hace que bash
trate esa línea como un comentario.
El carácter #
al final de la línea bash
se usa para comentar el carácter /r
, como señaló Brian Tompsett en su respuesta . Sin esto, bash
arrojará un error si el archivo tiene /r/n
terminaciones de línea, requeridas por cmd
.
Al hacer # 2>nul
, estamos engañando a cmd
para que ignore el error de un comando #
inexistente, mientras se sigue ejecutando el comando que sigue.
No use esta solución si hay un comando #
disponible en la PATH
o si no tiene control sobre los comandos disponibles para cmd
.
Usar echo
para ignorar el carácter #
en cmd
Podemos usar echo
con su salida redirigida para insertar comandos cmd
en el área comentada de bash
:
echo >/dev/null # >nul & echo Hello cmd! & rem ^
echo ''Hello bash!'' #
Como el carácter #
no tiene un significado especial en cmd
, se trata como una parte del texto para hacer echo
. Todo lo que teníamos que hacer era redirigir la salida del comando echo
e insertar otros comandos después.
Archivo vacío #.bat
echo >/dev/null # 1>nul 2> #.bat
# & echo Hello cmd! & del #.bat & rem ^
echo ''Hello bash!'' #
La línea echo >/dev/null # 1>nul 2> #.bat
crea un archivo #.bat
vacío mientras está en cmd
(o reemplaza el #.bat
existente, si corresponde), y no hace nada mientras está en bash
.
Este archivo será utilizado por las líneas de cmd
que siguen, incluso si hay algún otro comando #
en la PATH
.
El comando del #.bat
en el código específico de cmd
elimina el archivo que se creó. Solo tienes que hacer esto en la última línea de cmd
.
No use esta solución si un archivo #.bat
podría estar en su directorio de trabajo actual, ya que ese archivo se borrará.
Recomendado: usando heredocs para ignorar los comandos de cmd
en bash
:; echo ''Hello bash!'';<<:
echo Hello cmd! & ^
:
Al colocar el carácter ^
al final de la línea de cmd
, estamos escapando del salto de línea, y al usar :
como el delimitador de documento aquí, el contenido de la línea de delimitador no tendrá efecto en cmd
. De esta forma, cmd
solo ejecutará su línea después de que :
línea haya terminado, teniendo el mismo comportamiento que bash
.
Si desea tener varias líneas en ambas plataformas y solo ejecutarlas al final del bloque, puede hacer esto:
:;( #
:; echo ''Hello'' #
:; echo ''bash!'' #
:; );<<''here-document delimiter''
(
echo Hello
echo cmd!
) & rem ^
here-document delimiter
Mientras no haya una línea de cmd
con el here-document delimiter
, esta solución debería funcionar. Puede cambiar here-document delimiter
a cualquier otro texto.
En todas las soluciones presentadas, los comandos solo se ejecutarán después de la última línea , haciendo que su comportamiento sea consistente si hacen lo mismo en ambas plataformas.
Esas soluciones se deben guardar en archivos con /r/n
como saltos de línea, de lo contrario no funcionarán en cmd
.
Las respuestas anteriores parecen cubrir prácticamente todas las opciones y me ayudaron mucho. Incluyo esta respuesta aquí solo para demostrar el mecanismo que solía incluir tanto un script Bash como un script CMD de Windows en el mismo archivo.
LinuxWindowsScript.bat
echo >/dev/null # >nul & GOTO WINDOWS & rem ^
echo ''Processing for Linux''
# ***********************************************************
# * NOTE: If you modify this content, be sure to remove carriage returns (/r)
# * from the Linux part and leave them in together with the line feeds
# * (/n) for the Windows part. In summary:
# * New lines in Linux: /n
# * New lines in Windows: /r/n
# ***********************************************************
# Do Linux Bash commands here... for example:
StartDir="$(pwd)"
# Then, when all Linux commands are complete, end the script with ''exit''...
exit 0
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
:WINDOWS
echo "Processing for Windows"
REM Do Windows CMD commands here... for example:
SET StartDir=%cd%
REM Then, when all Windows commands are complete... the script is done.
Resumen
En Linux
La primera línea ( echo >/dev/null # >nul & GOTO WINDOWS & rem ^
) será ignorada y la secuencia de comandos fluirá a través de cada línea inmediatamente después hasta que se ejecute el comando exit 0
. Una vez que se llega a la exit 0
, la ejecución del script finalizará, ignorando los comandos de Windows que se encuentran debajo.
En Windows
La primera línea ejecutará el comando GOTO WINDOWS
, omitiendo los comandos de Linux que lo siguen inmediatamente y continuando la ejecución en la línea :WINDOWS
.
Eliminar devoluciones de carro en Windows
Dado que estaba editando este archivo en Windows, tuve que eliminar sistemáticamente los retornos de carro (/ r) de los comandos de Linux o de lo contrario obtuve resultados anormales al ejecutar la porción de Bash. Para hacer esto, abrí el archivo en Notepad++ e hice lo siguiente:
Active la opción para ver los caracteres de fin de línea (
View
>Show Symbol
>Show End of Line
). Los retornos de carro se mostrarán como caracteresCR
.Haga una búsqueda y reemplazo (
Search
>Replace...
) y marque la opciónExtended (/n, /r, /t, /0, /x...)
.Escriba
/r
en el campoFind what :
y elimine el campoReplace with :
para que no haya nada en él.Comenzando en la parte superior del archivo, haga clic en el botón
Replace
hasta que todos los caracteres de retorno de carro (CR
) se hayan eliminado de la parte superior de Linux. Asegúrese de dejar los caracteres de retorno de carro (CR
) para la porción de Windows.
El resultado debería ser que cada comando de Linux termina en solo un avance de línea ( LF
) y cada comando de Windows termina en un retorno de carro y avance de línea ( CR
LF
).
Lo que he hecho es usar la sintaxis de la etiqueta de cmd como marcador de comentarios . El carácter de etiqueta, dos puntos (:), es equivalente a true
en la mayoría de las shells POSIX. Si inmediatamente sigues el carácter de la etiqueta por otro personaje que no puede usarse en un GOTO
, comentar tu script cmd
no debería afectar tu código cmd
.
El truco consiste en poner líneas de código después de la secuencia de caracteres " :;
". Si está escribiendo sobre todo guiones de una sola línea o, como puede ser el caso, puede escribir una línea de sh
para muchas líneas de cmd
, lo siguiente podría estar bien. No olvides que cualquier uso de $?
debe estar antes de su próximo colón :
porque :
restablece $?
a 0
:; echo "Hi, I’m ${SHELL}."; exit $?
@ECHO OFF
ECHO I''m %COMSPEC%
¿Un ejemplo muy artificioso de guardar $?
:
:; false; ret=$?
:; [ ${ret} = 0 ] || { echo "Program failed with code ${ret}." >&2; exit 1; }
:; exit
ECHO CMD code.
Otra idea para omitir el código cmd
es usar heredocs para que sh
trate el código cmd
como una cadena no utilizada y cmd
interpreta. En este caso, nos aseguramos de que el delimitador de nuestro heredoc sea citado (para evitar que sh
haga cualquier tipo de interpretación sobre su contenido cuando se ejecuta con sh
) y comienza con :
para que cmd
salte como cualquier otra línea que comience con :
:; echo "I am ${SHELL}"
:<<"::CMDLITERAL"
ECHO I am %COMSPEC%
::CMDLITERAL
:; echo "And ${SHELL} is back!"
:; exit
ECHO And back to %COMSPEC%
Dependiendo de sus necesidades o estilo de codificación, el código cmd
y sh
entrelazado puede o no tener sentido. El uso de heredocs es un método para realizar dicho entrelazado. Sin embargo, esto podría extenderse con la técnica GOTO
:
:<<"::CMDLITERAL"
@ECHO OFF
GOTO :CMDSCRIPT
::CMDLITERAL
echo "I can write free-form ${SHELL} now!"
if :; then
echo "This makes conditional constructs so much easier because"
echo "they can now span multiple lines."
fi
exit $?
:CMDSCRIPT
ECHO Welcome to %COMSPEC%
Los comentarios universales, por supuesto, se pueden hacer con la secuencia de caracteres : #
o :;#
. El espacio o punto y coma son necesarios porque sh
considera que #
es parte de un nombre de comando si no es el primer carácter de un identificador. Por ejemplo, es posible que desee escribir comentarios universales en las primeras líneas de su archivo antes de usar el método GOTO
para dividir su código. Luego puede informar a su lector de por qué su guión está escrito de forma tan extraña:
: # This is a special script which intermixes both sh
: # and cmd code. It is written this way because it is
: # used in system() shell-outs directly in otherwise
: # portable code. See https://.com/questions/17510688
: # for details.
:; echo "This is ${SHELL}"; exit
@ECHO OFF
ECHO This is %COMSPEC%
Por lo tanto, algunas ideas y formas de realizar scripts compatibles con sh
y cmd
sin graves efectos secundarios hasta donde yo sé (y sin tener cmd
output ''#'' is not recognized as an internal or external command, operable program or batch file.
).
Lo siguiente funciona para mí sin ningún error o mensaje de error con Bash 4 y Windows 10, a diferencia de las respuestas anteriores. Puse nombre al archivo "whatever.cmd", hago chmod +x
para que sea ejecutable en Linux, y lo hago tener terminaciones de línea Unix ( dos2unix
) para mantener bash quiet.
:; if [ -z 0 ]; then
@echo off
goto :WINDOWS
fi
if [ -z "$2" ]; then
echo "usage: $0 <firstArg> <secondArg>"
exit 1
fi
# bash stuff
exit
:WINDOWS
if [%2]==[] (
SETLOCAL enabledelayedexpansion
set usage="usage: %0 <firstArg> <secondArg>"
@echo !usage:"=!
exit /b 1
)
:: windows stuff
Puedes compartir variables:
:;SET() { eval $1; }
SET var=value
:;echo $var
:;exit
ECHO %var%
Quería comentar, pero solo puedo agregar una respuesta en este momento.
Las técnicas dadas son excelentes y las uso también.
Es difícil retener un archivo que contiene dos tipos de saltos de línea, siendo /n
para la parte bash y /r/n
para la parte windows. La mayoría de los editores intentan aplicar un esquema de salto de línea común adivinando qué tipo de archivo está editando. Además, la mayoría de los métodos de transferencia del archivo a través de Internet (particularmente como un archivo de texto o script) lavará los saltos de línea, por lo que podría comenzar con un tipo de salto de línea y terminar con el otro. Si hizo suposiciones acerca de los saltos de línea y luego le dio el guión a otra persona para que lo use, es posible que encuentre que no funciona para ellos.
El otro problema son los sistemas de archivos montados en red (o CD) que se comparten entre diferentes tipos de sistema (particularmente donde no se puede controlar el software disponible para el usuario).
Por lo tanto, uno debe usar el salto de línea DOS de /r/n
y también proteger el script bash del DOS /r
poniendo un comentario al final de cada línea ( #
). Tampoco puede usar las continuidades de línea en bash porque /r
hará que se rompan.
De esta forma, quien use el script y en cualquier entorno, funcionará.
¡Utilizo este método junto con Makefiles portátiles!
Utilizo esta técnica para crear archivos jar ejecutables. Como el archivo jar / zip se inicia en el encabezado zip, puedo poner un script universal para ejecutar este archivo en la parte superior:
#!/usr/bin/env sh
@ 2>/dev/null # 2>nul & echo off
:; alias ::=''''
:: exec java -jar $JAVA_OPTS "$0" "$@"
:: exit
java -jar %JAVA_OPTS% "%~dpnx0" %*
exit /B
- La primera línea hace eco en cmd y no imprime nada en sh. Esto se debe a que
@
in sh arroja un error que se canaliza a/dev/null
y luego comienza un comentario. En cmd, el conducto a/dev/null
falla porque el archivo no se reconoce en Windows, pero dado que Windows no detecta#
como comentario, el error se canaliza anul
. Luego hace un eco apagado. Como toda la línea está precedida por un@
, no obtiene printet en cmd. - El segundo define
::
, que comienza un comentario en cmd, a noop en sh. Esto tiene el beneficio de que::
no restablece$?
a0
Utiliza el truco ":;
es una etiqueta". - Ahora puedo anteponer comandos sh con
::
y se ignoran en cmd - On
:: exit
termina el script sh y puedo escribir comandos cmd - Solo la primera línea (shebang) es problemática en cmd ya que imprimirá el
command not found
. Tienes que decidirte a ti mismo si lo necesitas o no.
puedes probar esto:
#|| goto :batch_part
echo $PATH
#exiting the bash part
exit
:batch_part
echo %PATH%
Probablemente necesitará usar /r/n
como una nueva línea en lugar de un estilo de Unix. Si mal no recuerdo, la nueva línea de Unix no se interpreta como una nueva línea mediante .bat
Otra forma es crear un #.exe
archivo en la ruta que no hace nada de manera similar a mi respuesta aquí: ¿Es posible incrustar y ejecutar VBScript dentro de un archivo por lotes sin usar un archivo temporal?
EDITAR
La respuesta del binki es casi perfecta, pero aún se puede mejorar:
:<<BATCH
@echo off
echo %PATH%
exit /b
BATCH
echo $PATH
Utiliza de nuevo el truco y el comentario de varias líneas. Parece que cmd.exe (al menos en windows10) funciona sin problemas con los EOL de estilo de Unix, así que asegúrese de que su script se convierta en formato de Linux. (Se ha visto el mismo enfoque usado antes here y here ). Aunque usar shebang todavía producirá salida redundante ...