Ein einziges Skript für Windows Batch und Linux Bash?

96

Ist es möglich, eine einzelne Skriptdatei zu schreiben, die sowohl unter Windows (als .bat behandelt) als auch unter Linux (über Bash) ausgeführt wird?

Ich kenne die grundlegende Syntax von beiden, habe es aber nicht herausgefunden. Es könnte wahrscheinlich die obskure Syntax von Bash oder einen Fehler im Windows-Batch-Prozessor ausnutzen.

Der auszuführende Befehl kann nur eine einzelne Zeile zum Ausführen eines anderen Skripts sein.

Die Motivation besteht darin, nur einen einzigen Anwendungsstartbefehl für Windows und Linux zu haben.

Update: Das "native" Shell-Skript des Systems muss die richtige Interpreter-Version auswählen, bestimmten bekannten Umgebungsvariablen entsprechen usw. Die Installation zusätzlicher Umgebungen wie CygWin ist nicht vorzuziehen - ich möchte das Konzept beibehalten. " herunterladen & ausführen ".

Die einzige andere Sprache, die für Windows in Betracht gezogen werden muss, ist Windows Scripting Host - WSH, das seit 98 standardmäßig voreingestellt ist.

Ondra Žižka
quelle
9
Schauen Sie sich Perl oder Python an. Dies sind Skriptsprachen, die auf beiden Plattformen verfügbar sind.
a_horse_with_no_name
groovig, python, rubin jemand? stackoverflow.com/questions/257730/…
Kalpesh Soni
Groovy wäre standardmäßig großartig auf allen Systemen, ich würde es als Shell-Scripting-Sprache nehmen. Vielleicht eines Tages :)
Ondra Žižka
1
Siehe auch: github.com/BYVoid/Batsh
Dave Jarvis
2
Ein überzeugender Anwendungsfall hierfür sind Tutorials - um den Leuten zu sagen, dass sie "dieses kleine Skript ausführen", ohne erklären zu müssen, dass "wenn Sie unter Windows sind, verwenden Sie dieses kleine Skript, aber wenn Sie unter Linux oder Mac arbeiten, versuchen Sie dies Stattdessen habe ich es nicht getestet, weil ich unter Windows bin. " Leider verfügt Windows nicht über grundlegende Unix-ähnliche Befehle wie cpeingebaute oder umgekehrt, sodass das Schreiben von zwei separaten Skripten möglicherweise pädagogisch besser ist als die hier gezeigten fortgeschrittenen Techniken.
Qwertie

Antworten:

92

Ich habe die Beschriftungssyntax von cmd als Kommentarmarker verwendet . Das Beschriftungszeichen, ein Doppelpunkt ( :), entspricht den truemeisten POSIXish-Shells. Wenn Sie dem Beschriftungszeichen sofort ein anderes Zeichen folgen, das in a nicht verwendet werden kann, sollte das GOTOKommentieren Ihres cmdSkripts keinen Einfluss auf Ihren cmdCode haben.

Der Hack besteht darin, Codezeilen nach der Zeichenfolge " :;" zu setzen. Wenn Sie hauptsächlich einzeilige Skripte schreiben oder, wie es der Fall sein kann, eine Zeile shfür viele Zeilen von schreiben können, ist cmdFolgendes möglicherweise in Ordnung. Vergessen Sie nicht , dass die Verwendung von $?vor dem nächsten Doppelpunkt sein muss , :weil :Resets $?auf 0.

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

Ein sehr ausgeklügeltes Beispiel für Bewachung $?:

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

Eine andere Idee zum Überspringen von cmdCode ist die Verwendung von Heredocs, sodass shder cmdCode als nicht verwendete Zeichenfolge behandelt und cmdinterpretiert wird. In diesem Fall stellen wir sicher, dass das Trennzeichen unseres Heredocs in Anführungszeichen steht (um zu verhindern sh, dass der Inhalt beim Ausführen interpretiert wird sh) und mit beginnt, :sodass cmdes wie jede andere Zeile, die mit beginnt , überspringt :.

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

Abhängig von Ihren Anforderungen oder Ihrem Codierungsstil können Interlacing cmdund shCode sinnvoll sein oder auch nicht. Die Verwendung von Heredocs ist eine Methode, um ein solches Interlacing durchzuführen. Dies könnte jedoch mit der GOTOTechnik erweitert werden :

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

Universelle Kommentare können natürlich mit der Zeichenfolge : #oder erfolgen :;#. Das Leerzeichen oder Semikolon ist erforderlich, da es shals #Teil eines Befehlsnamens betrachtet wird, wenn es nicht das erste Zeichen eines Bezeichners ist. Beispielsweise möchten Sie möglicherweise universelle Kommentare in die ersten Zeilen Ihrer Datei schreiben, bevor Sie die GOTOMethode zum Teilen Ihres Codes verwenden. Dann können Sie Ihren Leser darüber informieren, warum Ihr Skript so seltsam geschrieben ist:

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

Daher einige Ideen und Möglichkeiten, um Skripte ohne schwerwiegende Nebenwirkungen zu erstellen shund zu cmdkompatibel zu machen, soweit ich weiß (und ohne cmdAusgabe '#' is not recognized as an internal or external command, operable program or batch file.).

Binki
quelle
5
Beachten Sie, dass das Skript eine .cmdErweiterung haben muss, da es sonst nicht unter Windows ausgeführt wird. Gibt es eine Problemumgehung?
Jviotti
1
Wenn Sie Batch-Dateien schreiben, würde ich empfehlen, sie nur zu benennen .cmdund als ausführbar zu markieren. Auf diese Weise funktioniert die Ausführung des Skripts über die Standard-Shell unter Windows oder Unix (nur mit Bash getestet). Da ich mir keine Möglichkeit vorstellen kann, einen Shebang einzuschließen, ohne dass sich cmd laut beschwert, müssen Sie das Skript immer über eine Shell aufrufen. Das heißt, stellen Sie sicher, dass Sie execlp()oder execvp()(ich denke system(), dies ist der „richtige“ Weg für Sie) anstelle von execl()oder verwenden execv()(diese letzteren Funktionen funktionieren nur bei Skripten mit Shebangs, da sie direkter zum Betriebssystem gehen).
Binki
Ich habe die Diskussion über Dateierweiterungen ausgelassen, weil ich meinen portablen Code in Shellouts (z. B. <Exec/>MSBuild Task ) und nicht als unabhängige Batchdateien geschrieben habe. Vielleicht, wenn ich etwas Zeit in der Zukunft habe, werde ich die Antwort mit den Informationen in diesen Kommentaren aktualisieren ...
Binki
23

Sie können dies versuchen:

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

Wahrscheinlich müssen Sie /r/neine neue Zeile anstelle eines Unix-Stils verwenden. Wenn ich mich recht erinnere, wird die neue Unix-Zeile von .batSkripten nicht als neue Zeile interpretiert. Eine andere Möglichkeit besteht darin, eine #.exeDatei im Pfad zu erstellen , die nichts bewirkt Ähnlich wie meine Antwort hier: Ist es möglich, VBScript in eine Batch-Datei einzubetten und auszuführen, ohne eine temporäre Datei zu verwenden?

BEARBEITEN

Die Antwort des Binki ist fast perfekt, kann aber noch verbessert werden:

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

echo $PATH

Es verwendet wieder den :Trick und den mehrzeiligen Kommentar. Looks wie cmd.exe (zumindest unter Windows 10) funktionieren problemlos mit den EOLs im Unix-Stil. Stellen Sie daher sicher, dass Ihr Skript in das Linux-Format konvertiert ist. (Der gleiche Ansatz wurde bereits hier und hier verwendet ). Obwohl die Verwendung von shebang immer noch eine redundante Ausgabe erzeugt ...

npocmaka
quelle
3
/r/nam Ende von Zeilen verursacht viele Kopfschmerzen im Bash-Skript, es sei denn, Sie beenden jede Zeile mit einem #(dh #/r/n) - auf diese Weise \rwird das als Teil des Kommentars behandelt und ignoriert.
Gordon Davisson
2
Eigentlich finde ich, dass es # 2>NUL & ECHO Welcome to %COMSPEC%.gelingt, den Fehler zu verbergen , dass der #Befehl von nicht gefunden wird cmd. Diese Technik ist nützlich, wenn Sie eine eigenständige Zeile schreiben müssen, die nur von cmd(z. B. in a Makefile) ausgeführt wird.
Binki
11

Ich wollte einen Kommentar abgeben, kann aber momentan nur eine Antwort hinzufügen.

Die Techniken sind ausgezeichnet und ich benutze sie auch.

Es ist schwierig, eine Datei beizubehalten, in der zwei Arten von Zeilenumbrüchen enthalten sind, nämlich /nfür den Bash-Teil und /r/nfür den Windows-Teil. Die meisten Editoren versuchen, ein allgemeines Zeilenumbruchschema durchzusetzen, indem sie erraten, welche Art von Datei Sie bearbeiten. Die meisten Methoden zum Übertragen der Datei über das Internet (insbesondere als Text- oder Skriptdatei) waschen die Zeilenumbrüche, sodass Sie mit einer Art von Zeilenumbruch beginnen und mit der anderen enden können. Wenn Sie Annahmen über Zeilenumbrüche gemacht und Ihr Skript dann einer anderen Person zur Verwendung gegeben haben, stellt diese möglicherweise fest, dass es für sie nicht funktioniert.

Das andere Problem sind netzwerkmontierte Dateisysteme (oder CDs), die von verschiedenen Systemtypen gemeinsam genutzt werden (insbesondere, wenn Sie die dem Benutzer zur Verfügung stehende Software nicht steuern können).

Man sollte daher den DOS-Zeilenumbruch von verwenden /r/nund auch das Bash-Skript vor dem DOS schützen, /rindem man am Ende jeder Zeile einen Kommentar einfügt ( #). Sie können in bash auch keine Zeilenfortsetzungen verwenden, da /rdiese dadurch unterbrochen werden.

Auf diese Weise funktioniert es, wer auch immer das Skript verwendet und in welcher Umgebung auch immer.

Ich benutze diese Methode in Verbindung mit der Erstellung von tragbaren Makefiles!

Brian Tompsett - 汤 莱恩
quelle
6

Das Folgende funktioniert bei mir ohne Fehler oder Fehlermeldungen mit Bash 4 und Windows 10, im Gegensatz zu den obigen Antworten. Ich nenne die Datei "Whatever.cmd", chmod +xmache sie unter Linux ausführbar und mache sie mit Unix-Zeilenenden ( dos2unix), um Bash ruhig zu halten.

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

Sie können Variablen gemeinsam nutzen:

:;SET() { eval $1; }

SET var=value

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

Es gibt verschiedene Möglichkeiten, verschiedene Befehle für bashund auszuführencmd mit demselben Skript .

cmdignoriert Zeilen, die mit beginnen :;, wie in anderen Antworten erwähnt. Die nächste Zeile wird ebenfalls ignoriert, wenn die aktuelle Zeile mit dem Befehl endet rem ^, da das ^Zeichen dem Zeilenumbruch entgeht und die nächste Zeile von als Kommentar behandelt wirdrem .

Es gibt mehrere Möglichkeiten bash, die cmdZeilen zu ignorieren . Ich habe einige Möglichkeiten aufgezählt, um dies zu tun, ohne das zu brechen cmd Befehle :

Nicht existent # Befehl (nicht empfohlen)

Wenn beim Ausführen des Skripts kein #Befehl verfügbar cmdist, können wir Folgendes tun:

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

Das #Zeichen am cmdZeilenanfang machtbash behandelt diese Zeile als Kommentar.

Das #Zeichen am Ende der bashZeile wird verwendet, um das \rZeichen zu kommentieren , wie Brian Tompsett in seiner Antwort hervorhob . Andernfalls bashwird ein Fehler \r\nausgegeben, wenn die Datei Zeilenenden hat, die von erforderlich sind cmd.

Auf diese Weise # 2>nulversuchen wir cmd, den Fehler eines nicht vorhandenen #Befehls zu ignorieren , während der folgende Befehl ausgeführt wird.

Verwenden Sie diese Lösung nicht, wenn #auf dem Befehl ein Befehl verfügbar ist PATHoder wenn Sie keine Kontrolle über die verfügbaren Befehle haben cmd.


Verwenden Sie echo, um das #Zeichen zu ignorierencmd

Wir können echomit seiner umgeleiteten Ausgabe cmdBefehle in bashden auskommentierten Bereich einfügen :

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

Da das #Zeichen keine besondere Bedeutung hat cmd, wird es als Teil des Textes behandelt echo. Alles was wir tun mussten, ist die Ausgabe des echoBefehls umzuleiten und danach andere Befehle einzufügen.


Leere #.batDatei

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

Die echo >/dev/null # 1>nul 2> #.batZeile erstellt im eingeschalteten Zustand eine leere #.batDatei cmd(oder ersetzt vorhandene #.bat, falls vorhanden) und im eingeschalteten Zustand nichts bash.

Diese Datei wird von den folgenden cmdZeilen verwendet, auch wenn sich #auf dem Befehl ein anderer Befehl befindet PATH.

Der del #.batBefehl für den cmdspezifischen Code löscht die erstellte Datei. Sie müssen dies nur in der letzten cmdZeile tun .

Verwenden Sie diese Lösung nicht, wenn sich eine #.batDatei in Ihrem aktuellen Arbeitsverzeichnis befinden könnte, da diese Datei gelöscht wird.


Empfohlen: Verwenden Sie here-document , um cmdBefehle zu ignorierenbash

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

Wenn Sie das ^Zeichen am Ende der cmdZeile platzieren, entgehen wir dem Zeilenumbruch und wenn Sie es :als Trennzeichen für das Dokument verwenden, hat der Inhalt der Trennzeichenzeile keine Auswirkung auf cmd. Auf diese Weise cmdwird die :Zeile erst ausgeführt, nachdem die Zeile beendet ist, und hat das gleiche Verhalten wiebash .

Wenn Sie mehrere Zeilen auf beiden Plattformen haben und diese nur am Ende des Blocks ausführen möchten, können Sie Folgendes tun:

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

Solange es keine cmdgenaue Linie gibt here-document delimiter, sollte diese Lösung funktionieren. Sie können here-document delimiterzu jedem anderen Text wechseln .


In allen vorgestellten Lösungen werden die Befehle erst nach der letzten Zeile ausgeführt , wodurch ihr Verhalten konsistent wird, wenn sie auf beiden Plattformen dasselbe tun.

Diese Lösungen müssen \r\nals Zeilenumbrüche in Dateien gespeichert werden , da sie sonst nicht funktionieren cmd.

Tex Killer
quelle
Besser spät als nie ... das hat mir mehr geholfen als die anderen Antworten. Vielen Dank!!
A-Diddy
2

Die vorherigen Antworten scheinen so ziemlich alle Optionen abzudecken und haben mir sehr geholfen. Ich füge diese Antwort hier ein, um den Mechanismus zu demonstrieren, mit dem ich sowohl ein Bash-Skript als auch ein Windows-CMD-Skript in dieselbe Datei aufgenommen habe.

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.

Zusammenfassung

Unter Linux

Die erste Zeile ( echo >/dev/null # >nul & GOTO WINDOWS & rem ^) wird ignoriert und das Skript durchläuft jede unmittelbar darauf folgende Zeile, bis der exit 0Befehl ausgeführt wird. Sobald dies exit 0erreicht ist, wird die Skriptausführung beendet und die Windows-Befehle darunter ignoriert.

In Windows

In der ersten Zeile wird der GOTO WINDOWSBefehl ausgeführt, wobei die unmittelbar darauf folgenden Linux-Befehle übersprungen werden und die Ausführung in der :WINDOWSZeile fortgesetzt wird.

Wagenrücklauf in Windows entfernen

Da ich diese Datei unter Windows bearbeitet habe, musste ich die Zeilenumbrüche (\ r) systematisch aus den Linux-Befehlen entfernen, da sonst beim Ausführen des Bash-Teils ungewöhnliche Ergebnisse erzielt wurden. Zu diesem Zweck habe ich die Datei in Notepad ++ geöffnet und Folgendes ausgeführt:

  1. Schalten Sie die Option zum Anzeigen von Zeilenende - Zeichen ( View> Show Symbol> Show End of Line). Wagenrückläufe werden dann als CRZeichen angezeigt.

  2. Führen Sie ein Suchen & Ersetzen ( Search> Replace...) durch und aktivieren Sie die Extended (\n, \r, \t, \0, \x...)Option.

  3. Geben Sie \rdas Find what :Feld ein und löschen Sie das Replace with :Feld aus, damit nichts darin ist.

  4. Klicken Sie oben in der Datei auf die ReplaceSchaltfläche, bis alle Wagenrücklaufzeichen ( CR) aus dem oberen Linux-Bereich entfernt wurden. Stellen Sie sicher, dass die CRZeichen für Wagenrücklauf ( ) für den Windows-Teil belassen werden.

Das Ergebnis sollte sein, dass jeder Linux-Befehl nur in einem Zeilenvorschub endet ( LF) und jeder Windows-Befehl in einem Wagenrücklauf und einem Zeilenvorschub endet ( CR LF).

A-Diddy
quelle
1

Ich benutze diese Technik, um lauffähige JAR-Dateien zu erstellen. Da die jar / zip-Datei am zip-Header beginnt, kann ich ein universelles Skript einfügen, um diese Datei oben auszuführen:

#!/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
  • Die erste Zeile wird in cmd wiedergegeben und druckt nichts auf sh. Dies liegt daran, dass @in sh einen Fehler auslöst, an den weitergeleitet wird, /dev/nullund danach ein Kommentar beginnt. Auf cmd /dev/nullschlägt die Pipe fehl, da die Datei unter Windows nicht erkannt wird. Da Windows jedoch keinen #Kommentar erkennt, wird der Fehler an weitergeleitetnul . Dann gibt es ein Echo aus. Weil der ganzen Zeile ein vorangestellt ist@ , wird auf cmd kein Ausdruck erstellt.
  • Der zweite definiert ::, der einen Kommentar in cmd startet, noop in sh. Dies hat den Vorteil , dass ::nicht zurückgesetzt $?zu 0. Es verwendet die ":; Trick ist ein Etikett".
  • Jetzt kann ich sh Befehle mit voranstellen :: und sie werden in cmd ignoriert
  • Auf :: exit dem sh endet das Skript und ich kann cmd Befehle schreiben
  • Nur die erste Zeile (shebang) ist in cmd problematisch, da sie gedruckt wird command not found. Sie müssen selbst entscheiden, ob Sie es brauchen oder nicht.
LolHens
quelle
0

Ich brauchte dies für einige meiner Python-Paketinstallationsskripte. Die meisten Dinge zwischen sh und bat-Datei sind gleich, aber nur wenige Dinge wie die Fehlerbehandlung sind unterschiedlich. Eine Möglichkeit, dies zu tun, ist wie folgt:

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

Dann rufen Sie dies aus dem Bash-Skript auf:

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

Die Windows-Batchdatei sieht folgendermaßen aus:

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

Es gibt plattformunabhängige Build-Tools wie Ant oder Maven mit XML-Syntax (basierend auf Java). Sie können also alle Ihre Skripte in Ant oder Maven neu schreiben und sie trotz des OS-Typs ausführen. Oder Sie können einfach ein Ant-Wrapper-Skript erstellen, das den Betriebssystemtyp analysiert und das entsprechende Bat- oder Bash-Skript ausführt.

Monitor
quelle
5
Das scheint ein grober Missbrauch dieser Werkzeuge zu sein. Wenn Sie die eigentliche Frage vermeiden möchten (die in der nativen Shell ausgeführt wird), sollten Sie eine universelle Skriptsprache wie Python vorschlagen, nicht ein Build-Tool, das zufällig als Ersatz für eine ordnungsgemäße Skriptsprache missbraucht werden kann.
ArtOfWarfare