¿Un único script para ejecutar tanto en Windows batch como en Linux Bash?

96

¿Es posible escribir un solo 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 alguna falla del procesador por lotes de Windows.

El comando a ejecutar puede ser solo una línea para ejecutar otro script.

La motivación es tener un solo comando de arranque de la aplicación tanto para Windows como para Linux.

Actualización: La necesidad del script de shell "nativo" del sistema es que debe elegir la versión del intérprete correcta, ajustarse a ciertas variables de entorno conocidas, etc. No es preferible instalar entornos adicionales como CygWin; me gustaría mantener el concepto " descargar y ejecutar ".

El único otro lenguaje a considerar para Windows es Windows Scripting Host - WSH, que está preestablecido de forma predeterminada desde 98.

Ondra Žižka
fuente
9
Busque en Perl o Python. Esos son lenguajes de secuencias de comandos disponibles en ambas plataformas.
a_horse_with_no_name
maravilloso, pitón, rubí, ¿alguien? stackoverflow.com/questions/257730/…
Kalpesh Soni
Groovy sería genial tenerlo en todos los sistemas de forma predeterminada, lo tomaría como un lenguaje de script de shell. Quizás algún día :)
Ondra Žižka
1
Ver también: github.com/BYVoid/Batsh
Dave Jarvis
2
Un caso de uso convincente para esto son los tutoriales: para poder decirle a la gente "ejecute este pequeño script" sin tener que explicar que "si está en Windows, use este pequeño script, pero si está en Linux o Mac, intente esto en cambio, que en realidad no he probado porque estoy en Windows ". Lamentablemente, Windows no tiene comandos básicos tipo Unix como los cpintegrados, o viceversa, por lo que escribir dos scripts separados puede ser pedagógicamente mejor que las técnicas avanzadas que se muestran aquí.
Qwertie

Respuestas:

92

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 trueen la mayoría de shells POSIXish. Si sigue inmediatamente el carácter de la etiqueta por otro carácter que no se puede usar en a GOTO, comentar su cmdsecuencia de comandos no debería afectar su cmdcódigo.

El truco consiste en poner líneas de código después de la secuencia de caracteres “ :;”. Si está escribiendo principalmente scripts de una sola línea o, como puede ser el caso, puede escribir una línea de shpara muchas líneas de cmd, lo siguiente podría estar bien. No olvide que cualquier uso de $?debe estar antes de los siguientes dos puntos :porque se :restablece $?a 0.

:; echo "Hi, I’m ${SHELL}."; exit $?
@ECHO OFF
ECHO I'm %COMSPEC%

Un ejemplo muy elaborado de protección $?:

:; false; ret=$?
:; [ ${ret} = 0 ] || { echo "Program failed with code ${ret}." >&2; exit 1; }
:; exit
ECHO CMD code.

Otra idea para saltarse el cmdcódigo es usar heredocs para que shtrate el cmdcódigo como una cadena no utilizada y lo cmdinterprete. En este caso, nos aseguramos de que el delimitador de heredoc esté entre comillas (para evitar shhacer cualquier tipo de interpretación de su contenido cuando se ejecuta con sh) y comience con :para que lo cmdsalte 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%

Según sus necesidades o estilo de codificación, el entrelazado cmdy el shcódigo pueden tener sentido o no. El uso de heredocs es un método para realizar dicho entrelazado. Sin embargo, esto podría ampliarse con la GOTOtécnica :

:<<"::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 el punto y coma son necesarios porque se shconsidera #parte del nombre de un 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 GOTOmétodo para dividir su código. Luego, puede informar a su lector de por qué su guión está escrito de manera 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 /programming/17510688
: # for details.
:; echo "This is ${SHELL}"; exit
@ECHO OFF
ECHO This is %COMSPEC%

Por lo tanto, algunas ideas y formas de lograr shy cmdscripts compatibles sin efectos secundarios graves que yo sepa (y sin tener cmdsalida '#' is not recognized as an internal or external command, operable program or batch file.).

binki
fuente
5
Tenga en cuenta que esto requiere que el script tenga .cmdextensión, de lo contrario no se ejecutará en Windows. ¿Hay algún trabajo alrededor?
jviotti
1
Si está escribiendo archivos por lotes, recomendaría simplemente nombrarlos .cmdy marcarlos como ejecutables. De esa manera, la ejecución del script desde el shell predeterminado en Windows o Unix funcionará (solo se probó con bash). Debido a que no puedo pensar en una forma de incluir un shebang sin hacer que cmd se queje en voz alta, siempre debe invocar el script a través de shell. Es decir, asegúrese de usar execlp()o execvp()(creo que system()es la forma "correcta" para usted) en lugar de execl()o execv()(estas últimas funciones solo funcionarán en scripts con shebangs porque van más directamente al sistema operativo).
binki
Omití la discusión sobre las extensiones de archivo porque estaba escribiendo mi código portátil en shellouts (por ejemplo, <Exec/>MSBuild Task ) en lugar de como archivos por lotes independientes. Tal vez si tengo algo de tiempo en el futuro actualizaré la respuesta con la información en estos comentarios…
binki
23

puedes probar esto:

#|| goto :batch_part
 echo $PATH
#exiting the bash part
exit
:batch_part
 echo %PATH%

Probablemente necesitará usar /r/ncomo una nueva línea en lugar de un estilo Unix.Si recuerdo correctamente, los scripts no interpretan la nueva línea .batUnix como una nueva línea.Otra forma es crear un #.exearchivo en la ruta que no hace nada en 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

Vuelve a utilizar el :truco y el comentario de varias líneas. Parece que cmd.exe (al menos en Windows10) funciona sin problemas con las EOL de estilo Unix, así que asegúrese de que su script se convierta en formato Linux. (El mismo enfoque se ha visto utilizado antes aquí y aquí ). Aunque usar shebang todavía producirá una salida redundante ...

npocmaka
fuente
3
/r/nal final de las líneas causará muchos dolores de cabeza en el script bash a menos que termine cada línea con un #(ie #/r/n); de esa manera \r, se tratará como parte del comentario y se ignorará.
Gordon Davisson
2
En realidad, me parece que # 2>NUL & ECHO Welcome to %COMSPEC%.logra ocultar el error sobre el #comando que no encuentra cmd. Esta técnica es útil cuando necesita escribir una línea independiente que solo será ejecutada por cmd( por ejemplo, en a Makefile).
binki
11

Quería comentar, pero solo puedo agregar una respuesta por el momento.

Las técnicas dadas son excelentes y las uso también.

Es difícil retener un archivo que tiene dos tipos de saltos de línea dentro, el /nde la parte bash y /r/nla parte de Windows. La mayoría de los editores intentan hacer cumplir 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 para transferir el archivo a través de Internet (especialmente como un archivo de texto o script) blanquearán 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 sobre los saltos de línea y luego le dio su script a otra persona para que lo usara, es posible que descubra que no funciona para ellos.

El otro problema son los sistemas de archivos (o CD) montados en red que se comparten entre diferentes tipos de sistemas (especialmente cuando no se puede controlar el software disponible para el usuario).

Por lo tanto, uno debe usar el salto de línea de DOS /r/ny también proteger el script bash del DOS /rponiendo un comentario al final de cada línea ( #). Tampoco puede usar continuaciones de línea en bash porque /rhará que se rompan.

De esta manera, quien use el script, y en cualquier entorno, funcionará.

¡Utilizo este método junto con la creación de Makefiles portátiles!

Brian Tompsett - 汤 莱恩
fuente
6

Lo siguiente funciona para mí sin errores o mensajes de error con Bash 4 y Windows 10, a diferencia de las respuestas anteriores. Llamo al archivo "lo que sea.cmd", lo hago chmod +xpara que sea ejecutable en Linux, y lo hago con terminaciones de línea Unix ( dos2unix) para mantener a bash en silencio.

:; 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
Remik Ziemlinski
fuente
3

Puedes compartir variables:

:;SET() { eval $1; }

SET var=value

:;echo $var
:;exit
ECHO %var%
Velkan
fuente
2

Hay varias formas de ejecutar diferentes comandos en bashy cmdcon el mismo script.

cmdignorará las líneas que comienzan con :;, como se menciona en otras respuestas. También ignorará la siguiente línea si la línea actual termina con el comando rem ^, ya que el ^personaje escapará del salto de línea y la siguiente línea será tratada como un comentario por rem.

En cuanto a hacer bashignorar las cmdlíneas, hay varias formas. He enumerado algunas formas de hacerlo sin romper los cmdcomandos:

#Comando inexistente (no recomendado)

Si no hay ningún #comando disponible cmdcuando se ejecuta el script, podemos hacer esto:

# 2>nul & echo Hello cmd! & rem ^
echo 'Hello bash!' #

El #carácter al principio de la cmdlínea hace bashque trate esa línea como un comentario.

El #carácter al final de la bashlínea se usa para comentar el \rpersonaje, como señaló Brian Tompsett en su respuesta . Sin esto, basharrojará un error si el archivo tiene \r\nfinales de línea, requeridos por cmd.

Haciendo # 2>nul , estamos engañando cmdpara ignorar el error de algún #comando inexistente , mientras seguimos ejecutando el comando que sigue.

No use esta solución si hay un #comando disponible en el PATHo si no tiene control sobre los comandos disponibles paracmd .


Usando echopara ignorar el #personaje encmd

Podemos usar echocon su salida redirigida para insertar cmdcomandos enbash el área comentada:

echo >/dev/null # >nul & echo Hello cmd! & rem ^
echo 'Hello bash!' #

Dado que el #carácter no tiene un significado especial en cmd, se trata como parte del texto para echo. Todo lo que teníamos que hacer era redirigir la salida delecho comando e insertar otros comandos después.


#.batArchivo vacío

echo >/dev/null # 1>nul 2> #.bat
# & echo Hello cmd! & del #.bat & rem ^
echo 'Hello bash!' #

La echo >/dev/null # 1>nul 2> #.batlínea crea un #.batarchivo vacío mientras está encendido cmd(o reemplaza el existente#.bat , si lo hay) y no hace nada mientras está encendido bash.

Este archivo será utilizado por la cmd(s) línea (s) que sigue incluso si hay alguna otra# comando en el PATH.

El del #.batcomando en el cmdcódigo específico elimina el archivo que se creó. Solo tienes que hacer esto en la última cmdlínea.

No use esta solución si un #.batarchivo podría estar en su directorio de trabajo actual, ya que ese archivo se borrará.


Recomendado: usar here-document para ignorar cmdcomandos enbash

:; echo 'Hello bash!';<<:
echo Hello cmd! & ^
:

Al colocar el ^carácter al final de la cmdlínea, nos escapamos del salto de línea, y al usar :como delimitador del documento aquí, el contenido de la línea delimitador no tendrá ningún efecto sobre cmd. De esa manera, cmdsolo ejecutará su línea después de: 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

Siempre que no haya una cmdlínea con exactamente here-document delimiter, esta solución debería funcionar. Puede cambiar here-document delimitera cualquier otro texto.


En todas las soluciones presentadas, los comandos solo se ejecutarán después de la última línea , lo que hace que su comportamiento sea consistente si hacen lo mismo en ambas plataformas.

Esas soluciones deben guardarse en archivos con \r\nsaltos de línea, de lo contrario no funcionarán cmd.

Asesino de Tex
fuente
Más vale tarde que nunca ... esto me ayudó más que las otras respuestas. ¡¡Gracias!!
A-Diddy
2

Las respuestas anteriores parecen cubrir prácticamente todas las opciones y me ayudaron mucho. Incluyo esta respuesta aquí solo para demostrar el mecanismo que usé para incluir un script Bash y 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 el script fluirá a través de cada línea inmediatamente siguiente hasta que exit 0se ejecute el comando. Una vez que exit 0se alcanza, la ejecución del script terminará, ignorando los comandos de Windows debajo de él.

En Windows

La primera línea ejecutará el GOTO WINDOWScomando, omitiendo los comandos de Linux inmediatamente posteriores y continuando la ejecución en la :WINDOWSlínea.

Eliminación de devoluciones de carro en Windows

Como 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 parte Bash. Para hacer esto, abrí el archivo en Notepad ++ e hice lo siguiente:

  1. Activar la opción para la visualización de caracteres de fin de línea ( View> Show Symbol> Show End of Line). Los retornos de carro se mostrarán como CRcaracteres.

  2. Haga una búsqueda y reemplazo ( Search> Replace...) y marque la Extended (\n, \r, \t, \0, \x...)opción.

  3. Escribe \rel Find what :campo y en blanco el Replace with :campo para que no haya nada en él.

  4. Comenzando en la parte superior del archivo, haga clic en el Replacebotón hasta que todos los CRcaracteres de retorno de carro ( ) se hayan eliminado de la parte superior de Linux. Asegúrese de dejar los CRcaracteres de retorno de carro ( ) para la parte de Windows.

El resultado debería ser que cada comando de Linux termina en solo un salto de línea ( LF) y cada comando de Windows termina en un retorno de carro y un salto de línea ( CR LF).

A-Diddy
fuente
1

Utilizo esta técnica para crear archivos jar ejecutables. Dado que el archivo jar / zip comienza 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 se repite en cmd y no imprime nada en sh. Esto se debe a que @in sh arroja un error que se canaliza /dev/nully luego comienza un comentario. En cmd, la tubería /dev/nullfalla porque el archivo no se reconoce en Windows, pero como Windows no detecta #como comentario, se envía el error nul. Luego hace un eco. Debido a que toda la línea está precedida por un @, no se imprime en cmd.
  • El segundo define ::, que inicia un comentario en cmd, a noop en sh. Esto tiene la ventaja de que ::no se restablece $?a 0. Utiliza el :;truco " es una etiqueta".
  • Ahora puedo anteponer los comandos sh con ::y se ignoran en cmd
  • En :: exit el script sh termina y puedo escribir comandos cmd
  • Solo la primera línea (shebang) es problemática en cmd ya que se imprimirá command not found. Tienes que decidir tú mismo si lo necesitas o no.
LolHens
fuente
0

Necesitaba esto para algunos de mis scripts de instalación de paquetes de Python. La mayoría de las cosas entre el archivo sh y bat son iguales, pero pocas cosas como el manejo de errores son diferentes. Una forma de hacerlo es la siguiente:

common.inc
----------
common statement1
common statement2

Luego llamas a esto desde el script bash:

linux.sh
--------
# do linux specific stuff
...
# call common code
source common.inc

El archivo por lotes de Windows se ve así:

windows.bat
-----------
REM do windows specific things
...
# call common code
call common.inc
Shital Shah
fuente
-6

Existe una plataforma de herramientas de compilación independiente como Ant o Maven con sintaxis xml (basada en Java). Por lo tanto, puede reescribir todos sus scripts en Ant o Maven y ejecutarlos a pesar del tipo de sistema operativo. O simplemente puede crear un script de contenedor Ant, que analizará el tipo de sistema operativo y ejecutará el script bat o bash apropiado.

monitor
fuente
5
Eso parece un mal uso de esas herramientas. Si desea evitar la pregunta real (que se ejecuta en el shell nativo), debe sugerir un lenguaje de scripting universal, como python, no una herramienta de compilación que pueda ser mal utilizada como reemplazo de un lenguaje de scripting adecuado.
ArtOfWarfare