Frage Ein Skript, das sowohl in Windows Batch als auch Linux Bash ausgeführt wird?


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

Ich kenne die grundlegende Syntax von beiden, aber habe es nicht herausgefunden. Es könnte wahrscheinlich einige obszöne Bash-Syntax oder einige Windows-Batch-Prozessor-Glitch ausnutzen.

Der auszuführende Befehl kann nur eine einzige Zeile sein, um ein anderes Skript auszuführen.

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

Aktualisieren: Die Notwendigkeit für das "native" Shell-Skript des Systems besteht darin, dass es die richtige Interpreter-Version auswählen muss, die bestimmten bekannten Umgebungsvariablen entspricht. Das Installieren zusätzlicher Umgebungen wie CygWin ist nicht vorzuziehen - ich möchte das Konzept "download & Lauf".

Die einzige andere Sprache für Windows ist Windows Scripting Host - WSH, die seit 98 standardmäßig voreingestellt ist.


75
2017-07-07 09:05


Ursprung


Antworten:


Was ich getan habe ist Verwenden Sie die Markensyntax von cmd als Kommentarmarker. Das Label-Zeichen, ein Doppelpunkt (:), ist äquivalent zu true in den meisten POSIX-Shells. Wenn Sie dem Label-Zeichen unmittelbar durch ein anderes Zeichen folgen, das nicht in einem verwendet werden kann GOTOund kommentiere dann deine cmd Skript sollte nicht Ihre beeinflussen cmd Code.

Der Hack soll Codezeilen nach der Zeichenfolge einfügen ":;". Wenn Sie hauptsächlich Einzeiler-Skripte schreiben oder, je nachdem, können Sie eine Zeile schreiben sh für viele Zeilen von cmdFolgendes könnte in Ordnung sein. Vergiss nicht, dass jeder Gebrauch von $? muss vor deinem nächsten Doppelpunkt sein : weil : wird zurückgesetzt $? bis 0.

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

Ein sehr konstruiertes Beispiel der Bewachung $?:

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

Eine andere Idee zum Überspringen cmd Code ist zu verwenden Heredocs damit sh behandelt die cmd Code als nicht verwendete Zeichenfolge und cmd interpretiert es. In diesem Fall stellen wir sicher, dass unser Abgrenzungszeichen von heredoc in Anführungszeichen steht sh von irgendeiner Art von Interpretation ihrer Inhalte beim Laufen sh) und beginnt mit : damit cmd überspringt es wie jede andere Zeile beginnend mit :.

:; 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 Bedürfnissen oder dem Codierungsstil, Interlacing cmd und sh Code kann oder kann nicht sinnvoll sein. Die Verwendung von Heredocs ist eine Methode, um ein solches Interlacing durchzuführen. Dies könnte jedoch mit dem verlängert werden GOTO Technik:

:<<"::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 gemacht werden : # oder :;#. Der Raum oder das Semikolon sind notwendig, weil sh überlegt # Teil eines Befehlsnamens sein, wenn es nicht das erste Zeichen eines Bezeichners ist. Zum Beispiel möchten Sie möglicherweise universelle Kommentare in die ersten Zeilen Ihrer Datei schreiben, bevor Sie die GOTO Methode, um Ihren Code zu teilen. 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 https://stackoverflow.com/questions/17510688
: # for details.
:; echo "This is ${SHELL}"; exit
@ECHO OFF
ECHO This is %COMSPEC%

So, einige Ideen und Wege zu erreichen sh und cmd-kompatible Skripts ohne ernsthafte Nebenwirkungen, soweit ich weiß (und ohne cmdAusgabe '#' is not recognized as an internal or external command, operable program or batch file.).


65
2017-07-12 20:44



Sie können dies versuchen:

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

Wahrscheinlich müssen Sie verwenden /r/n als eine neue Zeile anstelle eines Unix-Stils.Wenn ich mich richtig erinnere die neue Zeile wird nicht als neue Zeile von interpretiert .bat Skripte.Ein anderer Weg ist ein #.exe Datei in den Pfad, der nichts in ähnlicher Weise wie meine Antwort hier tut: Ist es möglich, VBScript in eine Batch-Datei einzubetten und auszuführen, ohne eine temporäre Datei zu verwenden?

BEARBEITEN

Das Binki's Antwort ist fast perfekt, kann aber noch verbessert werden:

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

echo $PATH

Es benutzt wieder das : Trick und der multi line Kommentar. Schaut wie cmd.exe (mindestens auf windows10) funktioniert ohne Probleme mit den unix Art EOLs, also stellen Sie sicher, dass Ihr Skript in linux Format umgewandelt wird. (Der gleiche Ansatz wurde schon vorher verwendet Hier und Hier ). Obwohl die Verwendung von Shebang immer noch eine redundante Ausgabe erzeugt, ...


16
2017-07-07 09:29



Ich wollte kommentieren, kann aber im Moment nur eine Antwort hinzufügen.

Die Techniken sind ausgezeichnet und ich benutze sie auch.

Es ist schwierig, eine Datei zu behalten, in der zwei Arten von Zeilenumbrüchen enthalten sind /n für den Bash Teil und /r/n für den Fenster Teil. Die meisten Editoren versuchen, ein gemeinsames Zeilenumbruchschema durchzusetzen, indem sie erraten, welche Art von Datei Sie bearbeiten. Auch die meisten Methoden, die Datei über das Internet zu übertragen (insbesondere als Text- oder Skriptdatei), werden die Zeilenumbrüche bereinigen, sodass Sie mit einer Art Zeilenumbruch beginnen und mit der anderen enden können. Wenn Sie Annahmen über Zeilenumbrüche gemacht haben und Ihr Skript dann jemand anderem zur Verfügung gestellt haben, könnten sie herausfinden, dass es für sie nicht funktioniert.

Das andere Problem sind netzwerkgestützte Dateisysteme (oder CDs), die von verschiedenen Systemtypen gemeinsam genutzt werden (insbesondere, wenn Sie die für den Benutzer verfügbare Software nicht steuern können).

Man sollte daher den DOS-Zeilenumbruch von verwenden /r/n und schütze das Bash-Skript auch vor dem DOS /r indem Sie am Ende jeder Zeile einen Kommentar abgeben (#). Sie können Linienfortsetzungen in bash auch nicht verwenden, weil die /r wird sie brechen lassen.

Auf diese Weise wird jeder, der das Skript benutzt, und in welcher Umgebung auch immer, arbeiten.

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


10
2017-12-17 13:58



Das Folgende funktioniert bei mir im Gegensatz zu den obigen Antworten für mich ohne Fehler oder Fehlermeldungen mit Bash 4 und Windows 10. Ich benenne die Datei "whatever.cmd", tun chmod +x um es in Linux ausführbar zu machen, und es Unix-Zeilenenden (dos2unix) um ruhig zu bleiben.

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

6
2017-08-19 04:24



Es gibt verschiedene Möglichkeiten, verschiedene Befehle auszuführen bash und cmd mit dem gleichen Skript.

cmd ignoriert Zeilen, die mit beginnen :;, wie in anderen Antworten erwähnt. Es ignoriert auch die nächste Zeile, wenn die aktuelle Zeile mit dem Befehl endet rem ^, als die ^ Das Zeichen wird den Zeilenumbruch verlassen und die nächste Zeile wird als Kommentar behandelt rem.

Wie zum machen bash Ignoriere das cmd Linien gibt es mehrere Möglichkeiten. Ich habe einige Möglichkeiten aufgezählt, dies zu tun, ohne die cmdBefehle:

Nicht existent # Befehl (nicht empfohlen)

Wenn es keine gibt # Befehl verfügbar am cmd Wenn das Skript ausgeführt wird, können wir Folgendes tun:

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

Das # Charakter am Anfang der cmd Linie macht bash Behandle diese Zeile als Kommentar.

Das # Zeichen am Ende der bash Zeile wird verwendet, um die auskommentieren \r Charakter, wie Brian Tompsett darauf hingewiesen hat seine Antwort. Ohne das, bash wird einen Fehler auslösen, wenn die Datei hat \r\n Zeilenenden, benötigt von cmd.

Durch das Tun # 2>nulWir tricksen cmd den Fehler einiger nicht existierender ignorieren # Befehl, während noch der folgende Befehl ausgeführt wird.

Verwenden Sie diese Lösung nicht, wenn eine # Befehl verfügbar auf der PATH oder wenn Sie keine Kontrolle über die verfügbaren Befehle haben cmd.


Verwenden echo das ignorieren # Charakter an cmd

Wir können benutzen echo mit seiner Ausgabe umgeleitet, um einzufügen cmd Befehle an bash's kommentierter Bereich:

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

Seit der # Charakter hat keine besondere Bedeutung cmdEs wird als Teil des Textes behandelt echo. Alles, was wir tun mussten, ist die Ausgabe der echo Befehl und fügen Sie andere Befehle danach ein.


Leer #.bat Datei

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

Das echo >/dev/null # 1>nul 2> #.bat Linie erstellt ein leeres #.bat Datei während an cmd (oder ersetzt bestehende #.bat, falls vorhanden), und macht währenddessen nichts bash.

Diese Datei wird von der cmd Zeile (n), die folgt, auch wenn es andere gibt # Befehl auf dem PATH.

Das del #.bat Befehl auf dem cmd-spezifischen Code löscht die Datei, die erstellt wurde. Sie müssen dies nur am letzten tun cmd Linie.

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


Empfohlen: verwenden Here-Dokument ignorieren cmd Befehle an bash

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

Durch die Platzierung der ^ Zeichen am Ende der cmd Zeile wir entkommen die Zeilenumbruch, und mit Hilfe von : Als Delimiter des Here-Documents haben die Inhalte der Delimiter-Zeile keine Auswirkungen auf cmd. Dieser Weg, cmd wird nur seine Linie nach dem ausführen : Die Linie ist vorbei und hat das gleiche Verhalten wie bash.

Wenn Sie mehrere Zeilen auf beiden Plattformen haben möchten 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 gibt cmd Linie mit genau here-document delimiter, sollte diese Lösung funktionieren. Du kannst ändern here-document delimiter zu irgendeinem anderen Text.


In allen vorgestellten Lösungen Die Befehle werden erst nach der letzten Zeile ausgeführtund ihr Verhalten konsistent machen, wenn sie auf beiden Plattformen dasselbe tun.

Diese Lösungen müssen in Dateien mit gespeichert werden \r\n als Zeilenumbrüche, sonst werden sie nicht bearbeitet cmd.


2
2017-07-27 03:18



Sie können Variablen teilen:

:;SET() { eval $1; }

SET var=value

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

0
2017-10-04 12:44



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

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

In Linux

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

In Windows

Die erste Zeile führt die GOTO WINDOWS Befehl, überspringe die unmittelbar folgenden Linux Befehle und setze die Ausführung am :WINDOWS Linie.

Carriage Returns in Windows entfernen

Da ich diese Datei in Windows bearbeitet habe, musste ich die Wagenrücklaufzeichen (\ r) systematisch von den Linux-Befehlen entfernen, sonst kam es beim Ausführen des Bash-Teils zu abnormalen Ergebnissen. Um dies zu tun, öffnete ich die Datei in Notizblock ++ und tat folgendes:

  1. Aktivieren Sie die Option zum Anzeigen von Zeilenendenzeichen (View> Show Symbol > Show End of Line). Wagenrücklauf wird dann als angezeigt CR Figuren.

  2. Suche und Ersetzen (Search > Replace...) und überprüfen Sie die Extended (\n, \r, \t, \0, \x...) Möglichkeit.

  3. Art \r in dem Find what : Feld und lösche die Replace with : so dass nichts drin ist.

  4. Klicken Sie am Anfang der Datei auf das Symbol Replace bis alle Wagenrücklauf (CR) Zeichen wurden aus dem oberen Linux-Teil entfernt. Achten Sie darauf, die Wagenrückgabe zu verlassen (CR) Zeichen für den Windows-Teil.

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


0
2018-02-22 22:46