Un singolo script da eseguire sia in batch di Windows che in Linux Bash?


96

È possibile scrivere un singolo file di script che venga eseguito sia su Windows (trattato come .bat) che su Linux (tramite Bash)?

Conosco la sintassi di base di entrambi, ma non ho capito. Probabilmente potrebbe sfruttare l'oscura sintassi di Bash o qualche glitch del processore batch di Windows.

Il comando da eseguire può essere solo una singola riga per eseguire altri script.

La motivazione è avere un solo comando di avvio dell'applicazione sia per Windows che per Linux.

Aggiornamento: la necessità di uno script di shell "nativo" del sistema è che deve scegliere la versione corretta dell'interprete, conformarsi a determinate variabili di ambiente ben note, ecc. L'installazione di ambienti aggiuntivi come CygWin non è preferibile - vorrei mantenere il concetto " scarica ed esegui ".

L'unico altro linguaggio da considerare per Windows è Windows Scripting Host - WSH, che è preimpostato per impostazione predefinita dal 98.


9
Guarda in Perl o Python. Questi sono linguaggi di scripting disponibili su entrambe le piattaforme.
a_horse_with_no_name

groovy, python, ruby ​​chiunque? stackoverflow.com/questions/257730/…
Kalpesh Soni

Groovy sarebbe fantastico avere su tutti i sistemi per impostazione predefinita, lo prenderei come un linguaggio di scripting di shell. Forse un giorno :)
Ondra Žižka


2
Un caso d'uso convincente per questo sono i tutorial: per essere in grado di dire alle persone "esegui questo piccolo script" senza dover spiegare che "se sei su Windows, usa questo piccolo script, ma se sei su Linux o Mac, prova questo invece, che non ho effettivamente testato perché sono su Windows ". Purtroppo Windows non ha comandi di base simili a Unix come quelli cpintegrati, o viceversa, quindi scrivere due script separati potrebbe essere pedagogicamente migliore rispetto alle tecniche avanzate mostrate qui.
Qwertie

Risposte:


92

Quello che ho fatto è usare la sintassi dell'etichetta di cmd come indicatore di commento . Il carattere dell'etichetta, due punti ( :), è equivalente a truenella maggior parte delle shell POSIXish. Se segui immediatamente il carattere dell'etichetta con un altro carattere che non può essere utilizzato in a GOTO, commentare lo cmdscript non dovrebbe influire sul cmdcodice.

Il trucco è inserire righe di codice dopo la sequenza di caratteri " :;". Se stai scrivendo principalmente script di una riga o, come potrebbe essere il caso, puoi scrivere una riga di shper molte righe di cmd, quanto segue potrebbe andare bene. Non dimenticare che qualsiasi utilizzo di $?deve essere prima dei due punti successivi :perché si :reimposta $?a 0.

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

Un esempio molto artificioso di guardia $?:

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

Un'altra idea per saltare il cmdcodice è usare heredocs in modo che shtratti il cmdcodice come una stringa inutilizzata e lo cmdinterpreti. In questo caso, ci assicuriamo che il delimitatore del nostro heredoc sia sia citato (per smettere shdi fare qualsiasi tipo di interpretazione sul suo contenuto quando si esegue con sh) che inizi con in :modo che lo cmdsalti come qualsiasi altra riga che inizia con :.

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

A seconda delle esigenze o dello stile di codifica, l'interlacciamento cmde il shcodice possono o non possono avere senso. L'uso di heredocs è un metodo per eseguire tale interlacciamento. Questo però potrebbe essere esteso con la GOTOtecnica :

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

I commenti universali, ovviamente, possono essere fatti con la sequenza di caratteri : #o :;#. Lo spazio o il punto e virgola sono necessari perché shconsidera #di far parte di un nome di comando se non è il primo carattere di un identificatore. Ad esempio, potresti voler scrivere commenti universali nelle prime righe del file prima di utilizzare il GOTOmetodo per dividere il codice. Quindi puoi informare il tuo lettore del motivo per cui il tuo script è scritto in modo così strano:

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

Quindi, alcune idee e modi per realizzare she cmdscript compatibili senza gravi effetti collaterali per quanto ne so (e senza avere un cmdoutput '#' is not recognized as an internal or external command, operable program or batch file.).


5
Si noti che ciò richiede che lo script abbia .cmdun'estensione, altrimenti non verrà eseguito in Windows. C'è qualche soluzione alternativa?
jviotti

1
Se stai scrivendo file batch, ti consiglio di nominarli .cmde contrassegnarli come eseguibili. In questo modo, l'esecuzione dello script dalla shell predefinita su Windows o unix funzionerà (testato solo con bash). Poiché non riesco a pensare a un modo per includere uno shebang senza che cmd si lamenti ad alta voce, devi sempre richiamare lo script tramite shell. Cioè, assicurati di usare execlp()o execvp()(penso system()che questo sia il modo "giusto" per te) piuttosto che execl()o execv()(queste ultime funzioni funzioneranno solo su script con shebangs perché vanno più direttamente al sistema operativo).
binki

Ho omesso la discussione sulle estensioni dei file perché stavo scrivendo il mio codice portatile in shellouts (ad esempio, <Exec/>MSBuild Task ) piuttosto che come file batch indipendenti. Forse se avrò un po 'di tempo in futuro aggiornerò la risposta con le informazioni in questi commenti ...
binki

23

puoi provare questo:

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

Probabilmente dovrai usarla /r/ncome nuova riga invece di uno stile unix.Se ricordo di aver corretto, la nuova riga unix non viene interpretata come una nuova riga dagli .batscript.Un altro modo è creare un #.exefile nel percorso che non fa nulla in modo simile alla mia risposta qui: è possibile incorporare ed eseguire VBScript all'interno di un file batch senza utilizzare un file temporaneo?

MODIFICARE

La risposta del binki è quasi perfetta ma può ancora essere migliorata:

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

echo $PATH

Usa ancora il :trucco e il commento su più righe. Sembra che cmd.exe (almeno su Windows10) funzioni senza problemi con gli EOL in stile unix, quindi assicurati che il tuo script sia convertito in formato Linux. (lo stesso approccio è stato visto prima qui e qui ). Sebbene l'utilizzo di shebang produrrà comunque un output ridondante ...


3
/r/nalla fine delle righe causerà molti mal di testa nello script bash a meno che non finiate ogni riga con un #(cioè #/r/n) - in questo modo \rverrà trattato come parte del commento e ignorato.
Gordon Davisson

2
In realtà, trovo che # 2>NUL & ECHO Welcome to %COMSPEC%.riesca a nascondere l'errore relativo al #comando non trovato da cmd. Questa tecnica è utile quando è necessario scrivere una riga autonoma che verrà eseguita solo da cmd(ad esempio, in a Makefile).
binki

11

Volevo commentare, ma al momento posso solo aggiungere una risposta.

Le tecniche fornite sono eccellenti e le uso anche io.

È difficile mantenere un file che contiene due tipi di interruzioni di riga, ovvero /nper la parte bash e /r/nper la parte Windows. La maggior parte degli editor cerca di applicare uno schema comune di interruzione di riga indovinando il tipo di file che si sta modificando. Inoltre, la maggior parte dei metodi di trasferimento del file su Internet (in particolare come file di testo o script) riciclerà le interruzioni di riga, quindi potresti iniziare con un tipo di interruzione di riga e finire con l'altro. Se hai fatto supposizioni sulle interruzioni di riga e poi hai dato il tuo script a qualcun altro perché lo usasse, potrebbero scoprire che non funziona per loro.

L'altro problema sono i file system montati in rete (o CD) condivisi tra diversi tipi di sistema (in particolare dove non è possibile controllare il software disponibile per l'utente).

Si dovrebbe quindi usare l'interruzione di riga del DOS /r/ne proteggere anche lo script bash dal DOS /rinserendo un commento alla fine di ogni riga (# ). Non è inoltre possibile utilizzare le continuazioni di riga in bash perché le /rcauserà l'interruzione.

In questo modo, chiunque utilizzi lo script, e in qualunque ambiente, funzionerà.

Uso questo metodo insieme alla creazione di Makefile portatili!


6

Quanto segue funziona per me senza errori o messaggi di errore con Bash 4 e Windows 10, a differenza delle risposte sopra. Io chiamo il file "any.cmd", faccio chmod +xper renderlo eseguibile in Linux, e gli faccio avere terminazioni di riga unix ( dos2unix) per mantenere bash silenzioso.

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

3

Puoi condividere variabili:

:;SET() { eval $1; }

SET var=value

:;echo $var
:;exit
ECHO %var%

2

Esistono diversi modi per eseguire comandi diversi sullo stesso script bashe cmdcon lo stesso.

cmdignorerà le righe che iniziano con :;, come menzionato in altre risposte. Ignorerà anche la riga successiva se la riga corrente termina con il comando rem ^, poiché il ^carattere sfuggirà all'interruzione di riga e la riga successiva verrà trattata come un commento da rem.

Per quanto riguarda la possibilità di bashignorare le cmdrighe, ci sono diversi modi. Ho elencato alcuni modi per farlo senza interrompere i cmdcomandi:

#Comando inesistente (sconsigliato)

Se non è #disponibile alcun comando cmdquando lo script viene eseguito, possiamo farlo:

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

Il #carattere all'inizio della cmdriga fa in modo bashche la riga venga trattata come un commento.

Il #carattere alla fine della bashriga viene utilizzato per commentare il \rpersonaggio, come ha sottolineato Brian Tompsett nella sua risposta . Senza questo, bashverrà generato un errore se il file ha \r\nterminazioni di riga, richieste da cmd.

In questo modo # 2>nul, stiamo cercando cmddi ignorare l'errore di un #comando inesistente , mentre eseguiamo ancora il comando che segue.

Non utilizzare questa soluzione se è #disponibile un comando su PATHo se non hai il controllo sui comandi disponibili per cmd.


Usando echoper ignorare il #personaggiocmd

Possiamo usare echocon il suo output reindirizzato per inserire cmdcomandi bashnell'area commentata di:

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

Poiché il #carattere non ha un significato speciale cmd, viene considerato come una parte del testo echo. Tutto quello che dovevamo fare era reindirizzare l'output del echocomando e inserire altri comandi dopo di esso.


#.batFile vuoto

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

La echo >/dev/null # 1>nul 2> #.batriga crea un #.batfile vuoto mentre è attiva cmd(o sostituisce esistente #.bat, se presente) e non fa nulla mentre è attiva bash.

Questo file verrà utilizzato dalle cmdrighe che seguono anche se sono presenti altri #comandi nel file PATH.

Il del #.batcomando sul cmdcodice -specific cancella il file che è stato creato. Devi solo farlo sull'ultima cmdriga.

Non utilizzare questa soluzione se un #.batfile potrebbe trovarsi nella directory di lavoro corrente, poiché tale file verrà cancellato.


Consigliato: usare here-document per ignorare i cmdcomandi subash

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

Posizionando il ^carattere alla fine della cmdriga si evita l'interruzione di riga e utilizzando :come delimitatore here-document, il contenuto della riga delimitatore non avrà alcun effetto su cmd. In questo modo, cmdeseguirà la sua riga solo dopo che la :riga è finita, con lo stesso comportamento di bash.

Se vuoi avere più righe su entrambe le piattaforme ed eseguirle solo alla fine del blocco, puoi farlo:

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

Finché non c'è una cmdlinea con esattamente here-document delimiter, questa soluzione dovrebbe funzionare. Puoi passare here-document delimitera qualsiasi altro testo.


In tutte le soluzioni presentate, i comandi verranno eseguiti solo dopo l'ultima riga , rendendo il loro comportamento coerente se fanno la stessa cosa su entrambe le piattaforme.

Queste soluzioni devono essere salvate su file con \r\ninterruzioni di riga, altrimenti non funzioneranno cmd.


Meglio tardi che mai ... questo mi ha aiutato più delle altre risposte. Grazie!!
A-Diddy

2

Le risposte precedenti sembrano coprire praticamente tutte le opzioni e mi hanno aiutato molto. Includo questa risposta qui solo per dimostrare il meccanismo che ho usato per includere sia uno script Bash che uno script CMD di Windows nello stesso file.

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.

Sommario

In Linux

La prima riga ( echo >/dev/null # >nul & GOTO WINDOWS & rem ^) verrà ignorata e lo script scorrerà attraverso ogni riga immediatamente successiva fino a quando il exit 0comando non viene eseguito. Una volta exit 0raggiunto, l'esecuzione dello script terminerà, ignorando i comandi di Windows sottostanti.

In Windows

La prima riga eseguirà il GOTO WINDOWScomando, saltando i comandi Linux immediatamente successivi e continuando l'esecuzione sulla :WINDOWSriga.

Rimozione dei ritorni a capo in Windows

Dato che stavo modificando questo file in Windows, ho dovuto rimuovere sistematicamente i ritorni a capo (\ r) dai comandi Linux, altrimenti ho ottenuto risultati anomali durante l'esecuzione della parte Bash. Per fare ciò, ho aperto il file in Notepad ++ e ho fatto quanto segue:

  1. Attivare l'opzione per la visualizzazione di caratteri di fine linea ( View> Show Symbol> Show End of Line). I ritorni a capo verranno quindi visualizzati come CRcaratteri.

  2. Fai un Trova e sostituisci ( Search> Replace...) e seleziona l' Extended (\n, \r, \t, \0, \x...)opzione.

  3. Digita \rnel Find what :campo e cancella il Replace with :campo in modo che non contenga nulla.

  4. Partendo dall'inizio del file, fare clic sul Replacepulsante finché tutti i caratteri di ritorno a CRcapo ( ) non sono stati rimossi dalla parte superiore di Linux. Assicurati di lasciare i caratteri di ritorno a capo ( CR) per la parte Windows.

Il risultato dovrebbe essere che ogni comando Linux termina con un semplice avanzamento riga ( LF) e ogni comando Windows termina con un ritorno a capo e un avanzamento riga ( CR LF).


1

Uso questa tecnica per creare file jar eseguibili. Poiché il file jar / zip inizia nell'intestazione zip, posso inserire uno script universale per eseguire questo file in alto:

#!/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 prima riga echeggia in cmd e non stampa nulla su sh. Questo perché @in sh genera un errore che viene reindirizzato /dev/nulle successivamente inizia un commento. Su cmd la pipe /dev/nullnon riesce perché il file non viene riconosciuto su Windows ma poiché Windows non rileva #come commento l'errore viene reindirizzato nul. Quindi fa un eco. Poiché l'intera riga è preceduta da un @non viene visualizzato il printet su cmd.
  • Il secondo definisce ::, che avvia un commento in cmd, a noop in sh. Questo ha il vantaggio che ::non si reimposta $?a 0. Usa il :;trucco " è un'etichetta".
  • Ora posso anteporre i comandi sh con ::e vengono ignorati in cmd
  • Sullo :: exitscript sh finisce e posso scrivere comandi cmd
  • Solo la prima riga (shebang) è problematica in cmd poiché verrà stampata command not found. Devi decidere da solo se ne hai bisogno o no.

0

Ne avevo bisogno per alcuni dei miei script di installazione del pacchetto Python. La maggior parte delle cose tra sh e bat file sono le stesse, ma poche cose come la gestione degli errori sono diverse. Un modo per farlo è il seguente:

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

Quindi lo chiami dallo script bash:

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

Il file batch di Windows ha questo aspetto:

windows.bat
-----------
REM do windows specific things
...
# call common code
call common.inc


-6

Esistono strumenti di compilazione indipendenti dalla piattaforma come Ant o Maven con sintassi xml (basato su Java). Quindi, potresti riscrivere tutti i tuoi script in Ant o Maven ed eseguirli nonostante il tipo di sistema operativo. Oppure puoi semplicemente creare uno script wrapper Ant, che analizzerà il tipo di sistema operativo ed eseguirà lo script bat o bash appropriato.


5
Sembra un grave uso improprio di quegli strumenti. Se si desidera evitare la domanda effettiva (in esecuzione nella shell nativa), è necessario suggerire un linguaggio di scripting universale, come python, non uno strumento di compilazione che può essere utilizzato in modo improprio in sostituzione di un linguaggio di scripting appropriato.
ArtOfWarfare
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.