Script único para rodar em lote do Windows e Linux Bash?

96

É possível escrever um único arquivo de script que seja executado no Windows (tratado como .bat) e no Linux (via Bash)?

Eu conheço a sintaxe básica de ambos, mas não descobri. Ele provavelmente poderia explorar alguma sintaxe obscura do Bash ou alguma falha do processador de lote do Windows.

O comando a ser executado pode ser apenas uma única linha para executar outro script.

A motivação é ter apenas um único comando de inicialização do aplicativo para Windows e Linux.

Atualização: A necessidade de um script de shell "nativo" do sistema é que ele precisa escolher a versão correta do interpretador, em conformidade com certas variáveis ​​de ambiente conhecidas, etc. A instalação de ambientes adicionais como o CygWin não é preferível - eu gostaria de manter o conceito " baixar e executar ".

O único outro idioma a ser considerado para o Windows é o Windows Scripting Host - WSH, que é predefinido por padrão desde 98.

Ondra Žižka
fonte
9
Procure em Perl ou Python. Essas são linguagens de script disponíveis em ambas as plataformas.
a_horse_with_no_name
groovy, python, ruby ​​alguém? stackoverflow.com/questions/257730/…
Kalpesh Soni
Seria ótimo ter Groovy em todos os sistemas por padrão, eu consideraria isso uma linguagem de script de shell. Talvez algum dia :)
Ondra Žižka
1
Veja também: github.com/BYVoid/Batsh
Dave Jarvis
2
Um caso de uso atraente para isso são os tutoriais - ser capaz de apenas dizer às pessoas "execute este pequeno script" sem ter que explicar que "se você estiver no Windows, use este pequeno script, mas se você estiver no Linux ou Mac, tente isto em vez disso, que na verdade não testei porque estou no Windows. " Infelizmente, o Windows não tem comandos básicos do tipo Unix, como cpembutidos, ou vice-versa, então escrever dois scripts separados pode ser pedagogicamente melhor do que as técnicas avançadas mostradas aqui.
Qwertie 01 de

Respostas:

92

O que fiz foi usar a sintaxe de rótulo do cmd como marcador de comentário . O caractere de rótulo, dois pontos ( :), é equivalente truena maioria dos shells POSIXish. Se você seguir imediatamente o caractere do rótulo por outro caractere que não pode ser usado em a GOTO, comentar seu cmdscript não deve afetar seu cmdcódigo.

O truque é colocar linhas de código após a sequência de caracteres “ :;”. Se você está escrevendo principalmente scripts de uma linha ou, como pode ser o caso, pode escrever uma linha shpara várias linhas de cmd, o seguinte pode ser adequado. Não se esqueça de que qualquer uso de $?deve ser antes de seus próximos dois pontos :porque :redefine $?para 0.

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

Um exemplo muito inventado de guarda $?:

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

Outra ideia para pular cmdcódigo é usar heredocs para que shtrate o cmdcódigo como uma string não usada e o cmdinterprete. Nesse caso, certificamo-nos de que o delimitador de nosso heredoc está entre aspas (para parar shde fazer qualquer tipo de interpretação em seu conteúdo ao executar com sh) e começa com de :modo que o cmdpule como qualquer outra linha começando com :.

:; echo "I am ${SHELL}"
:<<"::CMDLITERAL"
ECHO I am %COMSPEC%
::CMDLITERAL
:; echo "And ${SHELL} is back!"
:; exit
ECHO And back to %COMSPEC%

Dependendo das suas necessidades ou estilo de codificação, o entrelaçamento cmde o shcódigo podem ou não fazer sentido. Usar heredocs é um método para realizar esse entrelaçamento. Isso poderia, no entanto, ser estendido com a 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%

Os comentários universais, é claro, podem ser feitos com a sequência de caracteres : #ou :;#. O espaço ou ponto-e-vírgula são necessários porque shconsidera #-se parte de um nome de comando se não for o primeiro caractere de um identificador. Por exemplo, você pode querer escrever comentários universais nas primeiras linhas de seu arquivo antes de usar o GOTOmétodo para dividir seu código. Em seguida, você pode informar ao leitor por que seu script foi escrito de forma tão estranha:

: # 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%

Assim, algumas idéias e maneiras de realizar she - cmdscripts compatíveis sem efeitos colaterais graves, pelo que eu sei (e sem cmdsaída '#' is not recognized as an internal or external command, operable program or batch file.).

binki
fonte
5
Observe que isso requer que o script tenha .cmdextensão, caso contrário, ele não será executado no Windows. Existe alguma solução alternativa?
jviotti
1
Se você estiver escrevendo arquivos em lote, recomendo apenas nomeá-los .cmde marcá-los como executáveis. Dessa forma, a execução do script do shell padrão no Windows ou no Unix funcionará (testado apenas com o bash). Como não consigo pensar em uma maneira de incluir um shebang sem fazer cmd reclamar em voz alta, você deve sempre invocar o script via shell. execlp()Ou seja, certifique-se de usar ou execvp()(acho que system()é a maneira “certa” para você) em vez de execl()ou execv()(essas últimas funções só funcionam em scripts com shebangs porque vão mais diretamente para o sistema operacional).
binki
Omiti a discussão sobre extensões de arquivo porque estava escrevendo meu código portátil em shellouts (por exemplo, <Exec/>MSBuild Task ) em vez de arquivos em lote independentes. Talvez se eu tiver algum tempo no futuro eu atualizarei a resposta com as informações desses comentários…
binki
23

você pode tentar isto:

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

Provavelmente você precisará usar /r/ncomo uma nova linha em vez de um estilo unix. Se bem me lembro, a nova linha unix não é interpretada como uma nova linha por .batscripts. Outra maneira é criar um #.exearquivo no caminho que não faz nada em maneira semelhante à minha resposta aqui: É possível incorporar e executar VBScript em um arquivo em lote sem usar um arquivo temporário?

EDITAR

A resposta do binki é quase perfeita, mas ainda pode ser melhorada:

:<<BATCH
    @echo off
    echo %PATH%
    exit /b
BATCH

echo $PATH

Ele usa novamente o :truque e o comentário de várias linhas. Parece cmd.exe (pelo menos no windows10) funciona sem problemas com os EOLs de estilo unix, então certifique-se de que seu script seja convertido para o formato linux. (a mesma abordagem já foi usada antes aqui e aqui ). Embora o uso de shebang ainda produza saída redundante ...

npocmaka
fonte
3
/r/nno final das linhas causará muitas dores de cabeça no script bash, a menos que você termine cada linha com um #(ou seja #/r/n) - dessa forma, o \rserá tratado como parte do comentário e ignorado.
Gordon Davisson
2
Na verdade, acho que # 2>NUL & ECHO Welcome to %COMSPEC%.consegue esconder o erro sobre o #comando não ser encontrado por cmd. Esta técnica é útil quando você precisa escrever uma linha independente que só será executada por cmd( por exemplo, em a Makefile).
binki
11

Eu queria comentar, mas só posso adicionar uma resposta no momento.

As técnicas dadas são excelentes e eu também as uso.

É difícil reter um arquivo que contém dois tipos de quebras de linha, sendo /npara a parte bash e /r/npara a parte windows. A maioria dos editores tenta impor um esquema de quebra de linha comum, adivinhando que tipo de arquivo você está editando. Além disso, a maioria dos métodos de transferência de arquivo pela Internet (particularmente como um arquivo de texto ou script) irá iniciar as quebras de linha, portanto, você pode começar com um tipo de quebra de linha e terminar com o outro. Se você fez suposições sobre quebras de linha e, em seguida, deu seu script para outra pessoa usar, ela pode descobrir que não funciona para ela.

O outro problema são os sistemas de arquivos montados em rede (ou CDs) que são compartilhados entre diferentes tipos de sistema (particularmente quando você não pode controlar o software disponível para o usuário).

Deve-se, portanto, usar a quebra de linha do DOS /r/ne também proteger o script bash do DOS /r, colocando um comentário no final de cada linha ( #). Você também não pode usar continuações de linha no bash porque o /rfará com que quebrem.

Desta forma, quem usar o script, em qualquer ambiente, ele funcionará.

Eu uso esse método junto com a criação de Makefiles portáteis!

Brian Tompsett - 汤 莱恩
fonte
6

O que segue funciona para mim sem erros ou mensagens de erro com Bash 4 e Windows 10, ao contrário das respostas acima. Eu chamo o arquivo de "qualquer coisa.cmd", faço chmod +xpara torná-lo executável no linux e faço com que tenha terminações de linha unix ( dos2unix) para manter o bash silencioso.

:; 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
fonte
3

Você pode compartilhar variáveis:

:;SET() { eval $1; }

SET var=value

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

Existem várias maneiras de executar comandos diferentes sobre bashe cmdcom o mesmo script.

cmdirá ignorar as linhas que começam com :;, conforme mencionado em outras respostas. Ele também irá ignorar a próxima linha se a linha atual terminar com o comando rem ^, pois o ^caractere escapará da quebra de linha e a próxima linha será tratada como um comentário de rem.

Quanto a fazer bashignorar as cmdlinhas, existem várias maneiras. Eu enumerei algumas maneiras de fazer isso sem quebrar os cmdcomandos:

#Comando inexistente (não recomendado)

Se não houver nenhum #comando disponível cmdquando o script for executado, podemos fazer o seguinte:

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

O #caractere no início da cmdlinha faz com bashque trate essa linha como um comentário.

O #personagem no final da bashlinha é usado para comentar o \rpersonagem, como Brian Tompsett apontou em sua resposta . Sem isso, bashserá gerado um erro se o arquivo tiver \r\nterminações de linha, exigidas por cmd.

Ao fazer # 2>nulisso, estamos enganando cmdpara ignorar o erro de algum #comando inexistente , enquanto ainda executamos o comando a seguir.

Não use esta solução se houver um #comando disponível no PATHou se você não tiver controle sobre os comandos disponíveis para cmd.


Usando echopara ignorar o #personagem emcmd

Podemos usar echocom sua saída redirecionada para inserir cmdcomandos na bashárea comentada de:

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

Como o #caractere não tem nenhum significado especial cmd, ele é tratado como parte do texto para echo. Tudo o que precisamos fazer é redirecionar a saída do echocomando e inserir outros comandos depois dele.


#.batArquivo vazio

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

A echo >/dev/null # 1>nul 2> #.batlinha cria um #.batarquivo vazio enquanto ligado cmd(ou substitui o existente #.bat, se houver) e não faz nada enquanto está ligado bash.

Este arquivo será usado pela cmd(s) linha (s) que segue, mesmo se houver algum outro #comando no PATH.

O del #.batcomando no cmdcódigo específico exclui o arquivo que foi criado. Você só precisa fazer isso na última cmdlinha.

Não use esta solução se um #.batarquivo puder estar em seu diretório de trabalho atual, pois esse arquivo será apagado.


Recomendado: usar here-document para ignorar cmdcomandos embash

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

Ao colocar o ^caractere no final da cmdlinha, estamos escapando da quebra de linha e usando :como delimitador here-document, o conteúdo da linha delimitadora não terá efeito cmd. Dessa forma, cmdsó executará sua linha após o término da :linha, tendo o mesmo comportamento que bash.

Se quiser ter várias linhas em ambas as plataformas e executá-las apenas no final do bloco, você pode fazer o seguinte:

:;( #
  :;  echo 'Hello'  #
  :;  echo 'bash!'  #
:; );<<'here-document delimiter'
(
      echo Hello
      echo cmd!
) & rem ^
here-document delimiter

Desde que não haja uma cmdlinha exata here-document delimiter, essa solução deve funcionar. Você pode mudar here-document delimiterpara qualquer outro texto.


Em todas as soluções apresentadas, os comandos só serão executados após a última linha , tornando seu comportamento consistente se fizerem a mesma coisa nas duas plataformas.

Essas soluções devem ser salvas em arquivos com \r\nquebras de linha, caso contrário, não funcionarão cmd.

Tex Killer
fonte
Mais vale tarde do que nunca ... isso me ajudou mais do que as outras respostas. Obrigado!!
A-Diddy de
2

As respostas anteriores parecem cobrir praticamente todas as opções e me ajudaram muito. Estou incluindo esta resposta aqui apenas para demonstrar o mecanismo que usei para incluir um script Bash e um script Windows CMD no mesmo arquivo.

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.

Resumo

Em Linux

A primeira linha ( echo >/dev/null # >nul & GOTO WINDOWS & rem ^) será ignorada e o script fluirá por cada linha imediatamente a seguir até que o exit 0comando seja executado. Assim que exit 0for atingido, a execução do script será encerrada, ignorando os comandos do Windows abaixo dele.

No Windows

A primeira linha executará o GOTO WINDOWScomando, pulando os comandos do Linux imediatamente a seguir e continuando a execução na :WINDOWSlinha.

Removendo devoluções de carro no Windows

Como estava editando este arquivo no Windows, tive que remover sistematicamente os retornos de carro (\ r) dos comandos do Linux ou obtive resultados anormais ao executar a parte do Bash. Para fazer isso, abri o arquivo no Notepad ++ e fiz o seguinte:

  1. Ative a opção para visualizar o final da linha ( View> Show Symbol> Show End of Line). Os retornos de carro serão exibidos como CRcaracteres.

  2. Faça um Localizar e Substituir ( Search> Replace...) e marque a Extended (\n, \r, \t, \0, \x...)opção.

  3. Digite \rno Find what :campo e em branco o Replace with :campo para que não haja nada nele.

  4. Começando na parte superior do arquivo, clique no Replacebotão até que todos os CRcaracteres de retorno de carro ( ) tenham sido removidos da parte superior do Linux. Certifique-se de deixar os CRcaracteres de retorno de carro ( ) para a parte do Windows.

O resultado deve ser que cada comando do Linux termina em apenas um avanço de linha ( LF) e cada comando do Windows termina em um retorno de carro e avanço de linha ( CR LF).

A-Diddy
fonte
1

Eu uso essa técnica para criar arquivos jar executáveis. Como o arquivo jar / zip começa no cabeçalho zip, posso colocar um script universal para executar este arquivo no topo:

#!/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
  • A primeira linha ecoa em cmd e não imprime nada em sh. Isso ocorre porque o @in sh gera um erro que é canalizado /dev/nulle, depois disso, um comentário é iniciado. No cmd, o pipe para /dev/nullfalha porque o arquivo não é reconhecido no Windows, mas como o Windows não detecta #como um comentário, o erro é redirecionado nul. Em seguida, ele faz um eco off. Como a linha inteira é precedida por um, @ela não é impressa no cmd.
  • O segundo define ::, que inicia um comentário em cmd, para noop em sh. Isso tem o benefício que ::não é redefinido $?como 0. Ele usa o :;truque " é um rótulo".
  • Agora posso acrescentar comandos sh ::e eles são ignorados no cmd
  • No :: exitas extremidades de script sh e posso escrever comandos cmd
  • Apenas a primeira linha (shebang) é problemática no cmd, pois será impressa command not found. Você tem que decidir por si mesmo se precisa ou não.
LolHens
fonte
0

Eu precisava disso para alguns dos meus scripts de instalação de pacote Python. A maioria das coisas entre o arquivo sh e bat são as mesmas, mas poucas coisas como o tratamento de erros são diferentes. Uma maneira de fazer isso é a seguinte:

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

Então você chama isso do script bash:

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

O arquivo em lote do Windows tem a seguinte aparência:

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

Existem ferramentas de construção independentes de plataforma como Ant ou Maven com sintaxe xml (baseada em Java). Assim, você pode reescrever todos os seus scripts no Ant ou Maven e executá-los, independentemente do tipo de sistema operacional. Ou você pode simplesmente criar um script Ant wrapper, que irá analisar o tipo de sistema operacional e executar o bat ou o script bash apropriado.

monitor
fonte
5
Isso parece um mau uso grosseiro dessas ferramentas. Se você quiser evitar a questão real (rodando no shell nativo), você deve sugerir uma linguagem de script universal, como python, não uma ferramenta de construção que pode ser usada indevidamente como um substituto para uma linguagem de script adequada.
ArtOfWarfare