Un seul script à exécuter à la fois dans Windows batch et Linux Bash?

96

Est-il possible d'écrire un seul fichier de script qui s'exécute à la fois sous Windows (traité comme .bat) et Linux (via Bash)?

Je connais la syntaxe de base des deux, mais je n'ai pas compris. Il pourrait probablement exploiter une syntaxe obscure de Bash ou un problème de processeur par lots Windows.

La commande à exécuter peut être juste une seule ligne pour exécuter un autre script.

La motivation est de n'avoir qu'une seule commande de démarrage d'application pour Windows et Linux.

Mise à jour: Le besoin du script shell "natif" du système est qu'il doit choisir la bonne version d'interpréteur, se conformer à certaines variables d'environnement bien connues, etc. L'installation d'environnements supplémentaires comme CygWin n'est pas préférable - j'aimerais garder le concept " télécharger et exécuter ".

Le seul autre langage à prendre en compte pour Windows est Windows Scripting Host - WSH, qui est prédéfini par défaut depuis 98.

Ondra Žižka
la source
9
Regardez dans Perl ou Python. Ce sont des langages de script disponibles sur les deux plates-formes.
a_horse_with_no_name
groovy, python, ruby ​​quelqu'un? stackoverflow.com/questions/257730/…
Kalpesh Soni
Groovy serait génial d'avoir sur tous les systèmes par défaut, je le prendrais comme un langage de script shell. Peut-être un jour :)
Ondra Žižka
1
Voir aussi: github.com/BYVoid/Batsh
Dave Jarvis
2
Un cas d'utilisation convaincant pour cela est les tutoriels - pour pouvoir simplement dire aux gens "exécutez ce petit script" sans avoir à expliquer que "si vous êtes sous Windows, utilisez ce petit script, mais si vous êtes sous Linux ou Mac, essayez ceci à la place, que je n'ai pas réellement testé parce que je suis sous Windows. " Malheureusement, Windows n'a pas de commandes de base de type Unix comme cpintégrées, ou vice versa, donc l'écriture de deux scripts séparés peut être pédagogiquement meilleure que les techniques avancées présentées ici.
Qwertie

Réponses:

92

Ce que j'ai fait, c'est d' utiliser la syntaxe d'étiquette de cmd comme marqueur de commentaire . Le caractère d'étiquette, un deux-points ( :), est équivalent à truedans la plupart des shells POSIXish. Si vous suivez immédiatement le caractère d'étiquette par un autre caractère qui ne peut pas être utilisé dans a GOTO, le commentaire de votre cmdscript ne devrait pas affecter votre cmdcode.

Le hack consiste à mettre des lignes de code après la séquence de caractères « :;». Si vous écrivez principalement des scripts à une seule ligne ou, comme cela peut être le cas, vous pouvez écrire une ligne de shpour plusieurs lignes de cmd, ce qui suit peut convenir. N'oubliez pas que toute utilisation de $?doit être avant votre prochain deux :- points car il est :remis $?à 0.

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

Un exemple très artificiel de gardiennage $?:

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

Une autre idée pour ignorer le cmdcode est d'utiliser heredocs afin de shtraiter le cmdcode comme une chaîne inutilisée et de l' cmdinterpréter. Dans ce cas, nous nous assurons que le délimiteur de notre heredoc est à la fois entre guillemets (pour éviter shde faire toute sorte d'interprétation de son contenu lors de l'exécution avec sh) et commence par :pour qu'il cmdsaute dessus comme toute autre ligne commençant par :.

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

Selon vos besoins ou votre style de codage, l'entrelacement cmdet le shcode peuvent ou non avoir un sens. L'utilisation d'heredocs est une méthode pour effectuer un tel entrelacement. Cela pourrait cependant être étendu avec la GOTOtechnique :

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

Les commentaires universels, bien sûr, peuvent être faits avec la séquence de caractères : #ou :;#. L'espace ou le point-virgule sont nécessaires car shconsidère #comme faisant partie d'un nom de commande si ce n'est pas le premier caractère d'un identifiant. Par exemple, vous souhaiterez peut-être écrire des commentaires universels dans les premières lignes de votre fichier avant d'utiliser la GOTOméthode pour fractionner votre code. Ensuite, vous pouvez informer votre lecteur de la raison pour laquelle votre script est écrit si curieusement:

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

Ainsi, quelques idées et façons d'accomplir des scripts compatibles shet cmdsans effets secondaires sérieux pour autant que je sache (et sans avoir de cmdsortie '#' is not recognized as an internal or external command, operable program or batch file.).

binki
la source
5
Notez que cela nécessite que le script ait une .cmdextension, sinon il ne fonctionnera pas sous Windows. Y a-t-il une solution de contournement?
jviotti
1
Si vous écrivez des fichiers batch, je recommanderais simplement de les nommer .cmdet de les marquer comme exécutables. De cette façon, l'exécution du script à partir du shell par défaut sous Windows ou sous Unix fonctionnera (testé uniquement avec bash). Parce que je ne peux pas penser à un moyen d'inclure un shebang sans que cmd se plaigne bruyamment, vous devez toujours invoquer le script via le shell. Par exemple, assurez-vous d'utiliser execlp()ou execvp()(je pense system()que c'est la «bonne» façon pour vous) plutôt que execl()ou execv()(ces dernières fonctions ne fonctionneront que sur les scripts avec shebangs car elles vont plus directement vers le système d'exploitation).
binki
J'ai omis de discuter des extensions de fichiers parce que j'écrivais mon code portable dans des shellouts (par exemple, <Exec/>MSBuild Task ) plutôt que sous forme de fichiers batch indépendants. Peut-être que si j'ai un peu de temps dans le futur, je mettrai à jour la réponse avec les informations contenues dans ces commentaires…
binki
23

vous pouvez essayer ceci:

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

Vous devrez probablement utiliser /r/ncomme nouvelle ligne au lieu d'un style unix.Si je me souviens bien, la nouvelle ligne unix n'est pas interprétée comme une nouvelle ligne par les .batscripts.Une autre façon est de créer un #.exefichier dans le chemin qui ne fait rien dans de la même manière que ma réponse ici: est-il possible d'intégrer et d'exécuter VBScript dans un fichier batch sans utiliser de fichier temporaire?

ÉDITER

La réponse du binki est presque parfaite mais peut encore être améliorée:

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

echo $PATH

Il utilise à nouveau l' :astuce et le commentaire sur plusieurs lignes. On dirait que cmd.exe (au moins sur windows10) fonctionne sans problème avec les EOL de style unix, alors assurez-vous que votre script est converti au format linux. (la même approche a déjà été utilisée ici et ici ). Bien que l'utilisation de shebang produise toujours une sortie redondante ...

npocmaka
la source
3
/r/nà la fin des lignes causera de nombreux maux de tête dans le script bash à moins que vous ne terminiez chaque ligne par un #(ie #/r/n) - de cette façon, le \rsera traité comme faisant partie du commentaire et ignoré.
Gordon Davisson
2
En fait, je trouve que cela # 2>NUL & ECHO Welcome to %COMSPEC%.parvient à masquer l'erreur concernant la #commande introuvable par cmd. Cette technique est utile lorsque vous devez écrire une ligne autonome qui ne sera exécutée que par cmd( par exemple, en a Makefile).
binki
11

Je voulais faire un commentaire, mais je ne peux ajouter qu'une réponse pour le moment.

Les techniques données sont excellentes et je les utilise aussi.

Il est difficile de conserver un fichier contenant deux types de sauts de ligne, celui /nde la partie bash et /r/nla partie windows. La plupart des éditeurs essaient d'appliquer un schéma de saut de ligne commun en devinant le type de fichier que vous éditez. De plus, la plupart des méthodes de transfert du fichier sur Internet (en particulier sous forme de fichier texte ou de script) blanchiront les sauts de ligne, vous pouvez donc commencer par un type de saut de ligne et finir par l'autre. Si vous avez fait des hypothèses sur les sauts de ligne et que vous avez ensuite donné votre script à quelqu'un d'autre, il se peut que cela ne fonctionne pas pour eux.

L'autre problème concerne les systèmes de fichiers (ou CD) montés sur le réseau qui sont partagés entre différents types de systèmes (en particulier lorsque vous ne pouvez pas contrôler les logiciels disponibles pour l'utilisateur).

Il faut donc utiliser le saut de ligne DOS /r/net protéger également le script bash du DOS /ren mettant un commentaire à la fin de chaque ligne ( #). Vous ne pouvez pas non plus utiliser de continuations de ligne dans bash car le/r entraînera à se casser.

De cette façon, quiconque utilise le script, et dans n'importe quel environnement, il fonctionnera alors.

J'utilise cette méthode en conjonction avec la création de Makefiles portables!

Brian Tompsett - 汤 莱恩
la source
6

Ce qui suit fonctionne pour moi sans aucune erreur ni message d'erreur avec Bash 4 et Windows 10, contrairement aux réponses ci-dessus. Je nomme le fichier "what.cmd", fais-le chmod +xpour le rendre exécutable sous Linux, et lui donne des fins de ligne unix ( dos2unix) pour que bash reste silencieux.

:; 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
la source
3

Vous pouvez partager des variables:

:;SET() { eval $1; }

SET var=value

:;echo $var
:;exit
ECHO %var%
Velkan
la source
2

Il existe plusieurs manières d'exécuter différentes commandes sur bashet cmdavec le même script.

cmdignorera les lignes commençant par :;, comme mentionné dans d'autres réponses. Il ignorera également la ligne suivante si la ligne actuelle se termine par la commande rem ^, car le ^caractère échappera au saut de ligne et la ligne suivante sera traitée comme un commentaire par rem.

Quant à faire bashignorer les cmdlignes, il existe de multiples façons. J'ai énuméré quelques façons de le faire sans casser les cmdcommandes:

#Commande inexistante (non recommandée)

S'il n'y a pas de #commande disponible cmdlors de l'exécution du script, nous pouvons le faire:

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

Le #caractère au début de la cmdligne permet de bashtraiter cette ligne comme un commentaire.

Le #caractère à la fin de la bashligne est utilisé pour commenter le \rpersonnage, comme l'a souligné Brian Tompsett dans sa réponse . Sans cela, bashgénérera une erreur si le fichier a des \r\nfins de ligne, requises par cmd.

Ce faisant # 2>nul, nous avons du mal cmdà ignorer l'erreur d'une #commande inexistante , tout en exécutant la commande qui suit.

N'utilisez pas cette solution si une #commande est disponible sur le PATHou si vous n'avez aucun contrôle sur les commandes disponibles pour cmd.


Utiliser echopour ignorer le #personnage surcmd

Nous pouvons utiliser echoavec sa sortie redirigée pour insérer des cmdcommandes dans bashla zone commentée de:

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

Étant donné que le #caractère n'a pas de signification particulière cmd, il est traité comme une partie du texte à echo. Tout ce que nous avions à faire était de rediriger la sortie de la echocommande et d'insérer d'autres commandes après.


#.batFichier vide

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

La echo >/dev/null # 1>nul 2> #.batligne crée un #.batfichier vide lorsqu'elle est activée cmd(ou remplace l'existant #.bat, le cas échéant) et ne fait rien lorsqu'elle est activée bash.

Ce fichier sera utilisé par la ou les cmdlignes qui suivent même s'il existe une autre #commande sur le PATH.

La del #.batcommande sur le cmdcode spécifique supprime le fichier qui a été créé. Vous ne devez le faire que sur la dernière cmdligne.

N'utilisez pas cette solution si un #.batfichier peut se trouver dans votre répertoire de travail actuel, car ce fichier sera effacé.


Recommandé: utiliser here-document pour ignorer les cmdcommandes surbash

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

En plaçant le ^caractère à la fin de la cmdligne, nous échappons au saut de ligne, et en utilisant :comme délimiteur ici-document, le contenu de la ligne de délimitation n'aura aucun effet sur cmd. De cette façon, cmdn'exécutera sa ligne qu'une fois la :ligne terminée, ayant le même comportement que bash.

Si vous souhaitez avoir plusieurs lignes sur les deux plates-formes et ne les exécuter qu'à la fin du bloc, vous pouvez le faire:

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

Tant qu'il n'y a pas de cmdligne avec exactement here-document delimiter, cette solution devrait fonctionner. Vous pouvez passer here-document delimiterà n'importe quel autre texte.


Dans toutes les solutions présentées, les commandes ne seront exécutées qu'après la dernière ligne , rendant leur comportement cohérent si elles font la même chose sur les deux plates-formes.

Ces solutions doivent être enregistrées dans des fichiers avec des \r\nsauts de ligne, sinon elles ne fonctionneront pas cmd.

Tex Killer
la source
Mieux vaut tard que jamais ... cela m'a aidé plus que les autres réponses. Merci!!
A-Diddy
2

Les réponses précédentes semblent couvrir à peu près toutes les options et m'ont beaucoup aidé. J'inclus cette réponse ici juste pour démontrer le mécanisme que j'ai utilisé pour inclure à la fois un script Bash et un script Windows CMD dans le même fichier.

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.

Résumé

Sous Linux

La première ligne ( echo >/dev/null # >nul & GOTO WINDOWS & rem ^) sera ignorée et le script parcourra chaque ligne qui la suivra immédiatement jusqu'à ce que la exit 0commande soit exécutée. Une fois exit 0atteint, l'exécution du script se terminera, ignorant les commandes Windows en dessous.

Sous Windows

La première ligne exécutera la GOTO WINDOWScommande, en ignorant les commandes Linux qui la suivent immédiatement et en poursuivant l'exécution sur la :WINDOWSligne.

Suppression des retours chariot dans Windows

Depuis que j'éditions ce fichier sous Windows, j'ai dû systématiquement supprimer les retours chariot (\ r) des commandes Linux ou bien j'obtenais des résultats anormaux lors de l'exécution de la partie Bash. Pour ce faire, j'ai ouvert le fichier dans Notepad ++ et j'ai fait ce qui suit:

  1. Activez l'option pour l' affichage des caractères de fin de ligne ( View> Show Symbol> Show End of Line). Les retours chariot s'afficheront alors sous forme de CRcaractères.

  2. Effectuez un Rechercher et remplacer ( Search> Replace...) et cochez l' Extended (\n, \r, \t, \0, \x...)option.

  3. Tapez \rle Find what :champ et videz le Replace with :champ pour qu'il n'y ait rien dedans.

  4. En commençant par le haut du fichier, cliquez sur le Replacebouton jusqu'à ce que tous les caractères de retour chariot ( CR) aient été supprimés de la partie supérieure de Linux. Veillez à laisser les caractères de retour chariot ( CR) pour la partie Windows.

Le résultat devrait être que chaque commande Linux se termine par un simple saut de ligne ( LF) et chaque commande Windows se termine par un retour chariot et un saut de ligne ( CR LF).

A-Diddy
la source
1

J'utilise cette technique pour créer des fichiers jar exécutables. Étant donné que le fichier jar / zip commence à l'en-tête zip, je peux mettre un script universel pour exécuter ce fichier en haut:

#!/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 première ligne fait écho dans cmd et n'imprime rien sur sh. C'est parce que le @in sh renvoie une erreur qui est redirigée vers /dev/nullet après cela, un commentaire démarre. Sur cmd, le canal /dev/nulléchoue car le fichier n'est pas reconnu sur Windows, mais comme Windows ne détecte pas #comme un commentaire, l'erreur est redirigée nul. Ensuite, il fait un écho. Parce que toute la ligne est précédée d'un, @il ne reçoit pas d'impression sur cmd.
  • Le second définit ::, qui commence un commentaire dans cmd, à noop dans sh. Cela a l'avantage qui ::ne se réinitialise $?pas 0. Il utilise l' :;astuce " est une étiquette".
  • Maintenant, je peux ajouter des commandes sh avec ::et elles sont ignorées dans cmd
  • Sur :: exitle script sh se termine et je peux écrire des commandes cmd
  • Seule la première ligne (shebang) pose problème dans cmd puisqu'elle s'imprimera command not found. Vous devez décider vous-même si vous en avez besoin ou non.
LolHens
la source
0

J'en avais besoin pour certains de mes scripts d'installation de package Python. La plupart des choses entre les fichiers sh et bat sont les mêmes, mais peu de choses comme la gestion des erreurs sont différentes. Une façon de procéder est la suivante:

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

Ensuite, vous appelez cela à partir du script bash:

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

Le fichier de commandes Windows ressemble à ceci:

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

Il existe des outils de construction indépendants de la plate-forme comme Ant ou Maven avec une syntaxe xml (basée sur Java). Ainsi, vous pouvez réécrire tous vos scripts dans Ant ou Maven et les exécuter malgré le type d'os. Ou vous pouvez simplement créer un script wrapper Ant, qui analysera le type d'os et exécutera le script bat ou bash approprié.

moniteur
la source
5
Cela semble être une grave mauvaise utilisation de ces outils. Si vous vouliez éviter la question réelle (exécutée dans le shell natif), vous devriez suggérer un langage de script universel, comme python, et non un outil de construction susceptible d'être utilisé à mauvais escient pour remplacer un langage de script approprié.
ArtOfWarfare