web-dev-qa-db-fra.com

Un seul script à exécuter en batch Windows et Linux Bash?

Est-il possible d'écrire un seul fichier de script qui s'exécute à la fois dans 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 technique lié au traitement par lots de Windows.

La commande à exécuter peut être une simple 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 script shell "natif" du système a besoin de choisir la bonne version d'interprète, de se conformer à certaines variables d'environnement bien connues, etc. L'installation d'environnements supplémentaires tels que CygWin n'est pas préférable - I ' d aimer garder le concept "download & run".

La seule autre langue à prendre en compte pour Windows est Windows Scripting Host - WSH, préconfiguré par défaut depuis 98.

85
Ondra Žižka

Ce que j’ai fait est tilisez la syntaxe d’étiquette de cmd comme marqueur de commentaire . Le caractère d'étiquette, un signe deux-points (:), Est équivalent à true dans la plupart des shells POSIXish. Si vous suivez immédiatement le caractère d’étiquette avec un autre caractère qui ne peut pas être utilisé dans un GOTO, le commentaire de votre script cmd ne devrait pas affecter votre code cmd.

Le hack consiste à mettre des lignes de code après la séquence de caractères ":;". Si vous écrivez principalement des scripts à une ligne ou, comme cela peut être le cas, pouvez écrire une ligne de sh pour de nombreuses lignes de cmd, les opérations suivantes pourraient bien se dérouler. N’oubliez pas que toute utilisation de $? Doit se faire avant votre prochain colon : Car : Réinitialise $? À 0.

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

Un exemple très artificiel de protection $?:

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

Une autre idée pour ignorer le code cmd consiste à utiliser heredocs afin que sh traite le code cmd comme une chaîne inutilisée et que cmd l'interprète. Dans ce cas, nous nous assurons que le délimiteur de notre heredoc est à la fois cité (pour empêcher sh de faire toute sorte d'interprétation sur son contenu lors de l'exécution avec sh) et commence par : de sorte que cmd le saute 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, le code cmd et sh entrelacé peut avoir un sens ou non. Utiliser heredocs est une méthode pour effectuer un tel entrelacement. Cela pourrait cependant être étendu avec la technique GOTO :

:<<"::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 :;#. Les espaces ou points-virgules sont nécessaires car sh considère que # Fait partie d'un nom de commande s'il ne s'agit pas du premier caractère d'un identificateur. Par exemple, vous pouvez écrire des commentaires universels dans les premières lignes de votre fichier avant d'utiliser la méthode GOTO pour scinder votre code. Ensuite, vous pourrez informer votre lecteur de la raison pour laquelle votre script est écrit de manière aussi étrange:

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

Ainsi, certaines idées et façons d'accomplir sh et cmd- des scripts compatibles sans effets secondaires graves pour autant que je sache (et sans avoir cmd output '#' is not recognized as an internal or external command, operable program or batch file. ).

76
binki

vous pouvez essayer ceci:

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

Vous devrez probablement utiliser /r/n comme une 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 .bat scripts. Une autre méthode consiste à créer un #.exe fichier dans le chemin qui ne fait rien de la même manière que ma réponse: Est-il possible d'incorporer et d'exécuter VBScript dans un fichier batch sans utiliser de fichier temporaire?

[~ # ~] éditer [~ # ~]

Le réponse de binki est presque parfait mais peut encore être amélioré:

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

echo $PATH

Il utilise à nouveau le : astuce et commentaire sur plusieurs lignes.L'apparence de cmd.exe (du moins sur Windows 10) fonctionne sans problème avec les EOL de style unix. Assurez-vous donc que votre script est converti au format Linux. (la même approche a été vue utilisée auparavant ici et ici ). Bien que l’utilisation de Shebang produise toujours une sortie redondante ...

19
npocmaka

Je voulais commenter, mais je peux seulement ajouter 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, à savoir /n pour la partie bash et /r/n pour la partie windows. La plupart des éditeurs essaient d'imposer un schéma de saut de ligne commun en devinant le type de fichier que vous modifiez. De plus, la plupart des méthodes de transfert du fichier sur Internet (notamment sous forme de fichier texte ou script) blanchissent les sauts de ligne. Vous pouvez donc commencer par un type de saut de ligne et finir par un autre. Si vous faites des suppositions sur les sauts de ligne et que vous donnez ensuite votre script à une autre personne, celle-ci trouvera peut-être que cela ne lui convient pas.

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

Il faut donc utiliser le saut de ligne DOS de /r/n et protège également le script bash du DOS /r en mettant un commentaire à la fin de chaque ligne (#). Vous ne pouvez pas non plus utiliser de continuations de lignes dans bash car le /r va les faire casser.

De cette manière, quiconque utilise le script et quel que soit l'environnement, il fonctionnera ensuite.

J'utilise cette méthode pour créer des Makefiles portables!

Ce qui suit fonctionne pour moi sans aucune erreur ou message d'erreur avec Bash 4 et Windows 10, contrairement aux réponses ci-dessus. Je nomme le fichier "what.cmd", do chmod +x pour le rendre exécutable sous Linux et lui donner une fin de ligne unix (dos2unix) garder bash tranquille.

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

Il existe plusieurs façons d’exécuter différentes commandes sur bash et cmd avec le même script.

cmd ignorera les lignes commençant par :;, comme indiqué dans d'autres réponses. Il ignorera également la ligne suivante si la ligne en cours 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 bash ignorer les lignes cmd, il y a plusieurs façons. J'ai énuméré plusieurs façons de le faire sans interrompre les commandes cmd:

Commande # Inexistante (non recommandé)

Si aucune commande # N'est disponible sur cmd lors de l'exécution du script, nous pouvons procéder comme suit:

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

Le caractère # Au début de la ligne cmd fait que bash traite cette ligne comme un commentaire.

Le caractère # À la fin de la ligne bash est utilisé pour commenter le caractère \r, Comme Brian Tompsett l'a souligné dans sa réponse . Sans cela, bash générera une erreur si le fichier comporte des fins de ligne \r\n, Requises par cmd.

En faisant # 2>nul, Nous incitons cmd à ignorer l'erreur d'une commande # Non existante, tout en exécutant la commande suivante.

N'utilisez pas cette solution s'il existe une commande # Disponible sur le PATH ou si vous n'avez aucun contrôle sur les commandes disponibles pour cmd.


Utiliser echo pour ignorer le caractère # Sur cmd

Nous pouvons utiliser echo avec sa sortie redirigée pour insérer des commandes cmd dans la zone commentée de bash:

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

Puisque le caractère # N'a pas de signification particulière sur cmd, il est traité comme une partie du texte de echo. Tout ce que nous avions à faire, c'était de rediriger le résultat de la commande echo et d'insérer d'autres commandes après celle-ci.


Vider le fichier #.bat

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

La ligne echo >/dev/null # 1>nul 2> #.bat Crée un fichier #.bat Vide alors que vous êtes sur cmd (ou remplace le #.bat Existant, le cas échéant) et ne fait rien lorsque vous êtes sur bash.

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

La commande del #.bat Du code spécifique à cmd supprime le fichier créé. Vous ne devez le faire que sur la dernière ligne cmd.

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


Recommandé: en utilisant here-document pour ignorer les commandes cmd sur bash

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

En plaçant le caractère ^ À la fin de la ligne cmd, nous échappons au saut de ligne et en utilisant : Comme délimiteur ici-document, le contenu de la ligne de délimiteur n'aura aucun effet sur cmd. De cette façon, cmd n'exécutera sa ligne qu'après la fin de la ligne :, Ayant le même comportement que bash.

Si vous voulez avoir plusieurs lignes sur les deux plates-formes et les exécuter uniquement à 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 ligne cmd avec exactement here-document delimiter, Cette solution devrait fonctionner. Vous pouvez remplacer here-document delimiter Par 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, ce qui rend leur comportement cohérent si ils font la même chose sur les deux plates-formes.

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

2
Tex Killer

Vous pouvez partager des variables:

:;SET() { eval $1; }

SET var=value

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

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 uniquement pour montrer le mécanisme que j'avais l'habitude d'inclure à la fois un script Bash et un script CMD Windows 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.

Sommaire

Sous Linux

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

Sous Windows

La première ligne exécute la commande GOTO WINDOWS, En ignorant les commandes Linux qui la suivent immédiatement et en continuant l'exécution à la ligne :WINDOWS.

Supprimer les retours chariot dans Windows

Depuis que je modifiais ce fichier sous Windows, je devais systématiquement supprimer les retours à la ligne (\ r) des commandes Linux ou obtenir des résultats anormaux lors de l'exécution de la partie Bash. Pour ce faire, j'ai ouvert le fichier dans Notepad ++ et fait ce qui suit:

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

  2. Faites une recherche et un remplacement (Search> Replace...) Et cochez l’option Extended (\n, \r, \t, \0, \x...).

  3. Tapez \r Dans le champ Find what : Et effacez le champ Replace with : Pour qu'il ne contienne rien.

  4. En commençant en haut du fichier, cliquez sur le bouton Replace jusqu'à ce que tous les caractères de retour chariot (CR) aient été supprimés de la partie supérieure de Linux. Assurez-vous de 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 saut de ligne (LF) et que chaque commande Windows se termine par un retour chariot et un saut de ligne (CRLF).

1
A-Diddy

J'utilise cette technique pour créer des fichiers jar exécutables. Puisque le fichier jar/Zip commence à l’en-tête du fichier 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. Cela est dû au fait que le @ De sh renvoie une erreur acheminée vers /dev/null Et qu'un commentaire commence ensuite. Sur cmd, le tube vers /dev/null Échoue car le fichier n'est pas reconnu par Windows, mais comme Windows ne détecte pas # En tant que commentaire, l'erreur est acheminée vers nul. Ensuite, il fait un écho. Parce que toute la ligne est précédée d'un @, Il n'y a pas printet sur cmd.
  • Le second définit ::, Qui commence un commentaire dans cmd, à noop in sh. Cela présente l'avantage que :: Ne réinitialise pas $? À 0. Il utilise le truc ":; Est une étiquette".
  • Maintenant, je peux ajouter des commandes sh avec :: Et elles sont ignorées dans cmd
  • Sur :: exit Le script sh se termine et je peux écrire des commandes cmd
  • Seule la première ligne (Shebang) pose problème dans cmd puisqu'elle affichera command not found. Vous devez décider vous-même si vous en avez besoin ou non.
0
LolHens