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.
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.
).
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 ...
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
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
:
#
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
.
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.
#.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é.
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
.
Vous pouvez partager des variables:
:;SET() { eval $1; }
SET var=value
:;echo $var
:;exit
ECHO %var%
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.
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.
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.
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
.
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:
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.
Faites une recherche et un remplacement (Search
> Replace...
) Et cochez l’option Extended (\n, \r, \t, \0, \x...)
.
Tapez \r
Dans le champ Find what :
Et effacez le champ Replace with :
Pour qu'il ne contienne rien.
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 (CR
LF
).
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
@
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.::
, 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".::
Et elles sont ignorées dans cmd:: exit
Le script sh se termine et je peux écrire des commandes cmdcommand not found
. Vous devez décider vous-même si vous en avez besoin ou non.