Analyse des arguments facultatifs du fichier Windows Bat

84

J'ai besoin de mon fichier bat pour accepter plusieurs arguments nommés facultatifs.

mycmd.bat man1 man2 -username alice -otheroption

Par exemple, ma commande a 2 paramètres obligatoires et deux paramètres optionnels (-username) qui ont une valeur d'argument alice et -otheroption:

J'aimerais être en mesure de regrouper ces valeurs en variables.

Il suffit d'appeler quiconque a déjà résolu ce problème. Mec, ces fichiers de chauve-souris sont une douleur.

poulet au poulet
la source
12
Une raison pour laquelle vous devez le faire dans un fichier BAT et ne pas dire dans VBScript? Il y a peut-être un moyen de faire cela dans un fichier BAT, mais en restant fidèle à l'approche du fichier BAT, vous «vous entrez dans un monde de douleur, fils». :-)
Alek Davis
Mieux encore, utilisez PowerShell. Il a une gestion des paramètres très avancée et intégrée.
Bill_Stewart
1
@AlekDavis, vous avez probablement raison, mais je suis très phobique en VBScript. Si jamais je me trouvais obligé d'utiliser VBScript, je pense que je serais déjà dans un monde de douleur. Je serai heureux de m'écrire un fichier batch pour automatiser quelque chose, alors peut-être que si je veux le rendre plus utile pour les gens, j'ajouterai des arguments. Souvent, certains d'entre eux sont facultatifs. À aucun moment depuis que j'ai quitté le monde bancaire, je n'ai pensé, ou quelqu'un m'a suggéré "vous voudrez du VBScript pour cela".
icc97
@chickeninabiscuit: le lien ne fonctionne plus. La version de la machine Wayback le fait. Je ne sais pas si je modifier votre commentaire si elle va changer à regarder comme je l'ai fait le travail ou non , je vais juste coller ici au lieu: http://web.archive.org/web/20090403050231/http://www.pcguide.com/vb/showthread.php?t=52323.
Brent Rittenhouse

Réponses:

112

Bien que j'ai tendance à être d'accord avec le commentaire de @AlekDavis , il existe néanmoins plusieurs façons de le faire dans le shell NT.

L'approche je tirerais parti de la commande SHIFT et du branchement conditionnel IF , quelque chose comme ça ...

@ECHO OFF

SET man1=%1
SET man2=%2
SHIFT & SHIFT

:loop
IF NOT "%1"=="" (
    IF "%1"=="-username" (
        SET user=%2
        SHIFT
    )
    IF "%1"=="-otheroption" (
        SET other=%2
        SHIFT
    )
    SHIFT
    GOTO :loop
)

ECHO Man1 = %man1%
ECHO Man2 = %man2%
ECHO Username = %user%
ECHO Other option = %other%

REM ...do stuff here...

:theend
ewall
la source
1
SHIFT /2décalages commençant à arg 2. Il ne se décale pas deux fois. Ne devrait-il pas être remplacé par SHIFT & SHIFT?
dbenham
2
OK - je vois pourquoi le code fonctionne généralement - le SHIFT dans la boucle déplace finalement les options vers arg 1 et aucun mal n'est fait (généralement). Mais le SHIFT / 2 initial est définitivement un bogue qui corrompra les résultats si la valeur arg 1 correspond à l'un des noms d'options. Essayez de courir mycmd.bat -username anyvalue -username myName -otheroption othervalue. Le résultat doit être user = myName, other = othervalue; mais le code donne user = -username, other = othervalue. Le bogue est corrigé en remplaçant SHIFT /2par SHIFT & SHIFT.
dbenham
2
@Christian - ewall est incorrect à propos de ;- il ne peut pas être utilisé à la place de &.
dbenham
1
C'est un bon début, mais je vois 3 problèmes. Premièrement: a) Après la ligne 10, %1et %2ont été «Utilisé». b) Le simple SHIFTde la ligne 11 déplace la valeur de %21 position vers le bas et est alors disponible (à nouveau) comme%1 , et la valeur "Inutilisé" de %3est disponible comme %2. c) La IFligne 13 voit cette valeur déjà "Utilisée" à partir de %2laquelle se trouve maintenant %1. d) Si le "nom d'utilisateur" de la ligne 10 ressemble à celui "-otheroption"-ci, il sera utilisé à nouveau et othersera mis à la nouvelle valeur dans %2, qui était probablement le nom de l'option suivante. --->
Kevin Fegan
2
Le deuxième problème: dans la question OP, le -otheroptionn'était pas suivi d'une valeur, il était donc probablement conçu comme une -Flagoption de type et ne devrait pas consommer un deuxième argument. 3ème: Si l'argument %1n'est pas traité par l'interneIF instructions , il est ignoré en silence. Le code devrait probablement afficher un message indiquant qu'il ne s'agissait pas d'une option valide. Cela pourrait être fait en ajoutant les instructions requises SHIFTet GOTO :loopdans les IFinstructions internes , ou en ajoutant une ELSEinstruction.
Kevin Fegan
74

La réponse choisie fonctionne, mais elle pourrait nécessiter une certaine amélioration.

  • Les options devraient probablement être initialisées aux valeurs par défaut.
  • Ce serait bien de conserver% 0 ainsi que les arguments requis% 1 et% 2.
  • Cela devient pénible d'avoir un bloc IF pour chaque option, d'autant plus que le nombre d'options augmente.
  • Ce serait bien d'avoir un moyen simple et concis de définir rapidement toutes les options et valeurs par défaut en un seul endroit.
  • Il serait bon de prendre en charge les options autonomes qui servent d'indicateurs (aucune valeur après l'option).
  • Nous ne savons pas si un argument est entre guillemets. Nous ne savons pas non plus si une valeur arg a été transmise à l'aide de caractères d'échappement. Mieux vaut accéder à un argument en utilisant% ~ 1 et mettre l'affectation entre guillemets. Ensuite, le lot peut s'appuyer sur l'absence de guillemets, mais les caractères spéciaux sont toujours généralement sûrs sans échapper. (Ce n'est pas à l'épreuve des balles, mais il gère la plupart des situations)

Ma solution repose sur la création d'une variable OPTIONS qui définit toutes les options et leurs valeurs par défaut. OPTIONS est également utilisé pour tester si une option fournie est valide. Une énorme quantité de code est enregistrée en stockant simplement les valeurs d'option dans des variables nommées de la même manière que l'option. La quantité de code est constante quel que soit le nombre d'options définies; seule la définition OPTIONS doit changer.

EDIT - De plus, le code de la boucle: doit changer si le nombre d'arguments de position obligatoires change. Par exemple, souvent, tous les arguments sont nommés, auquel cas vous voulez analyser les arguments commençant à la position 1 au lieu de 3. Ainsi, dans la boucle:, tous les 3 deviennent 1 et 4 devient 2.

@echo off
setlocal enableDelayedExpansion

:: Define the option names along with default values, using a <space>
:: delimiter between options. I'm using some generic option names, but 
:: normally each option would have a meaningful name.
::
:: Each option has the format -name:[default]
::
:: The option names are NOT case sensitive.
::
:: Options that have a default value expect the subsequent command line
:: argument to contain the value. If the option is not provided then the
:: option is set to the default. If the default contains spaces, contains
:: special characters, or starts with a colon, then it should be enclosed
:: within double quotes. The default can be undefined by specifying the
:: default as empty quotes "".
:: NOTE - defaults cannot contain * or ? with this solution.
::
:: Options that are specified without any default value are simply flags
:: that are either defined or undefined. All flags start out undefined by
:: default and become defined if the option is supplied.
::
:: The order of the definitions is not important.
::
set "options=-username:/ -option2:"" -option3:"three word default" -flag1: -flag2:"

:: Set the default option values
for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B"

:loop
:: Validate and store the options, one at a time, using a loop.
:: Options start at arg 3 in this example. Each SHIFT is done starting at
:: the first option so required args are preserved.
::
if not "%~3"=="" (
  set "test=!options:*%~3:=! "
  if "!test!"=="!options! " (
    rem No substitution was made so this is an invalid option.
    rem Error handling goes here.
    rem I will simply echo an error message.
    echo Error: Invalid option %~3
  ) else if "!test:~0,1!"==" " (
    rem Set the flag option using the option name.
    rem The value doesn't matter, it just needs to be defined.
    set "%~3=1"
  ) else (
    rem Set the option value using the option as the name.
    rem and the next arg as the value
    set "%~3=%~4"
    shift /3
  )
  shift /3
  goto :loop
)

:: Now all supplied options are stored in variables whose names are the
:: option names. Missing options have the default value, or are undefined if
:: there is no default.
:: The required args are still available in %1 and %2 (and %0 is also preserved)
:: For this example I will simply echo all the option values,
:: assuming any variable starting with - is an option.
::
set -

:: To get the value of a single parameter, just remember to include the `-`
echo The value of -username is: !-username!

Il n'y a vraiment pas beaucoup de code. La plupart des codes ci-dessus sont des commentaires. Voici exactement le même code, sans les commentaires.

@echo off
setlocal enableDelayedExpansion

set "options=-username:/ -option2:"" -option3:"three word default" -flag1: -flag2:"

for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B"
:loop
if not "%~3"=="" (
  set "test=!options:*%~3:=! "
  if "!test!"=="!options! " (
      echo Error: Invalid option %~3
  ) else if "!test:~0,1!"==" " (
      set "%~3=1"
  ) else (
      set "%~3=%~4"
      shift /3
  )
  shift /3
  goto :loop
)
set -

:: To get the value of a single parameter, just remember to include the `-`
echo The value of -username is: !-username!


Cette solution fournit des arguments de style Unix dans un lot Windows. Ce n'est pas la norme pour Windows - batch a généralement les options précédant les arguments requis et les options sont préfixées avec /.

Les techniques utilisées dans cette solution sont facilement adaptées à un style d'options Windows.

  • La boucle d'analyse recherche toujours une option à %1 , et continue jusqu'à ce que arg 1 ne commence pas par/
  • Notez que les affectations SET doivent être placées entre guillemets si le nom commence par /.
    SET /VAR=VALUEéchoue
    SET "/VAR=VALUE" fonctionne. Je fais déjà cela dans ma solution de toute façon.
  • Le style Windows standard exclut la possibilité que la première valeur d'argument requise commence par /. Cette limitation peut être éliminée en employant une //option implicitement définie qui sert de signal pour quitter la boucle d'analyse des options. Rien ne serait stocké pour //"l'option".


Mise à jour 28/12/2015: Prise en charge de! en valeurs d'option

Dans le code ci-dessus, chaque argument est développé tandis que l'expansion retardée est activée, ce qui signifie qu'il !est probablement supprimé, ou bien quelque chose comme !var!est développé. En outre, ^peut également être dépouillé s'il !est présent. La petite modification suivante du code non commenté supprime la limitation telle que !et ^sont conservés dans les valeurs d'option.

@echo off
setlocal enableDelayedExpansion

set "options=-username:/ -option2:"" -option3:"three word default" -flag1: -flag2:"

for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B"
:loop
if not "%~3"=="" (
  set "test=!options:*%~3:=! "
  if "!test!"=="!options! " (
      echo Error: Invalid option %~3
  ) else if "!test:~0,1!"==" " (
      set "%~3=1"
  ) else (
      setlocal disableDelayedExpansion
      set "val=%~4"
      call :escapeVal
      setlocal enableDelayedExpansion
      for /f delims^=^ eol^= %%A in ("!val!") do endlocal&endlocal&set "%~3=%%A" !
      shift /3
  )
  shift /3
  goto :loop
)
goto :endArgs
:escapeVal
set "val=%val:^=^^%"
set "val=%val:!=^!%"
exit /b
:endArgs

set -

:: To get the value of a single parameter, just remember to include the `-`
echo The value of -username is: !-username!
Dbenham
la source
Merci pour la solution très élégante, j'aime plus celle-ci que celle acceptée comme réponse. La seule chose que vous avez manquée est de dire comment utiliser les paramètres nommés dans le script batch, par exemple echo% -username%
Roboblob
1
Solution très élégante. Si vous envisagez d'utiliser ceci, sachez que le code fourni commencera à être analysé à l'argument 3. Ce n'est pas ce que vous voulez dans le cas général. Remplacez les "3" par "1" pour résoudre ce problème. @dbenham, ce serait bien si vous pouviez rendre cela plus évident dans le message original, je ne suis probablement pas le seul à être confus au début.
ocroquette
@ocroquette - Bon point. Je n'avais pas beaucoup de choix, étant donné que je devais répondre à la question précise. Mais le code doit être modifié lorsque le nombre d'arguments de position obligatoires change. Je vais essayer de modifier ma réponse pour que ce soit clair.
dbenham
J'aime cela. Merci pour la solution, cela fonctionne toujours en 2019 assez proprement pour certains scripts IDE!
Robert Smith
18

Si vous souhaitez utiliser des arguments facultatifs, mais pas des arguments nommés, cette approche a fonctionné pour moi. Je pense que c'est un code beaucoup plus facile à suivre.

REM Get argument values.  If not specified, use default values.
IF "%1"=="" ( SET "DatabaseServer=localhost" ) ELSE ( SET "DatabaseServer=%1" )
IF "%2"=="" ( SET "DatabaseName=MyDatabase" ) ELSE ( SET "DatabaseName=%2" )

REM Do work
ECHO Database Server = %DatabaseServer%
ECHO Database Name   = %DatabaseName%
charlie.mott
la source
IF "%1"==""échouera si l'argument contient lui-même des guillemets. Utilisez simplement un autre symbole non spécial à la place, comme ._ = [] # etc
Fr0sT
2
Je ne pense pas que cela fonctionne à moins que l'utilisateur ne sache utiliser "" à la place d'un argument. Sinon, comment puis-je définir un nom de base de données tout en laissant le serveur de base de données vide?
Sean Long
Sinon, comment voyez-vous une autre réponse y parvenir? @SeanLong
sehe
@SeanLong Right, donc l'utilisateur doit obéir à l'ordre. Vous devez donc mettre les arguments les plus demandés en première place.
Martin Braun
C'était certainement l'option la plus simple pour moi. Merci d'avoir posté :)
Steve Bauman
2

Création de variables dynamiques

entrez la description de l'image ici

Avantages

  • Fonctionne pour> 9 arguments
  • Conserve %1, %2... %*dans le tact
  • Fonctionne pour les deux /arget le -argstyle
  • Aucune connaissance préalable des arguments
  • La mise en œuvre est distincte de la routine principale

Les inconvénients

  • Les anciens arguments peuvent s'infiltrer dans des exécutions consécutives, donc utilisez-les setlocalpour la portée locale ou écrivez une :CLEAR-ARGSroutine d' accompagnement !
  • Pas encore de support d'alias (comme --forcepour -f)
  • Pas de ""support d'argument vide

Usage

Voici un exemple de relation entre les arguments suivants et les variables .bat:

>> testargs.bat /b 3 -c /d /e /f /g /h /i /j /k /bar 5 /foo "c:\"

echo %*        | /b 3 -c /d /e /f /g /h /i /j /k /bar 5 /foo "c:\"
echo %ARG_FOO% | c:\
echo %ARG_A%   |
echo %ARG_B%   | 3
echo %ARG_C%   | 1
echo %ARG_D%   | 1

la mise en oeuvre

@echo off
setlocal

CALL :ARG-PARSER %*

::Print examples
echo: ALL: %*
echo: FOO: %ARG_FOO%
echo: A:   %ARG_A%
echo: B:   %ARG_B%
echo: C:   %ARG_C%
echo: D:   %ARG_C%


::*********************************************************
:: Parse commandline arguments into sane variables
:: See the following scenario as usage example:
:: >> thisfile.bat /a /b "c:\" /c /foo 5
:: >> CALL :ARG-PARSER %*
:: ARG_a=1
:: ARG_b=c:\
:: ARG_c=1
:: ARG_foo=5
::*********************************************************
:ARG-PARSER
    ::Loop until two consecutive empty args
    :loopargs
        IF "%~1%~2" EQU "" GOTO :EOF

        set "arg1=%~1" 
        set "arg2=%~2"
        shift

        ::Allow either / or -
        set "tst1=%arg1:-=/%"
        if "%arg1%" NEQ "" (
            set "tst1=%tst1:~0,1%"
        ) ELSE (
            set "tst1="
        )

        set "tst2=%arg2:-=/%"
        if "%arg2%" NEQ "" (
            set "tst2=%tst2:~0,1%"
        ) ELSE (
            set "tst2="
        )


        ::Capture assignments (eg. /foo bar)
        IF "%tst1%" EQU "/"  IF "%tst2%" NEQ "/" IF "%tst2%" NEQ "" (
            set "ARG_%arg1:~1%=%arg2%"
            GOTO loopargs
        )

        ::Capture flags (eg. /foo)
        IF "%tst1%" EQU "/" (
            set "ARG_%arg1:~1%=1"
            GOTO loopargs
        )
    goto loopargs
GOTO :EOF

Simon Streicher
la source
Vous pouvez également déplacer le contenu de la :ARG-PARSERroutine vers un fichier .bat externe comme arg-parser.batpour le rendre également disponible pour d'autres scripts.
Simon Streicher
1

Une fois que j'ai écrit un programme qui gère les arguments courts (-h), longs (--help) et non-option dans le fichier de commandes. Ces techniques comprennent:

  • arguments non-option suivis par des arguments d'options.

  • opérateur shift pour les options qui n'ont pas d'argument comme '--help'.

  • deux opérateurs de décalage temporel pour les options qui nécessitent un argument.

  • boucle à travers une étiquette pour traiter tous les arguments de ligne de commande.

  • Quittez le script et arrêtez le traitement pour les options qui ne nécessitent aucune action supplémentaire comme «--help».

  • Rédaction de fonctions d'aide pour guider l'utilisateur

Voici mon code.

set BOARD=
set WORKSPACE=
set CFLAGS=
set LIB_INSTALL=true
set PREFIX=lib
set PROGRAM=install_boards

:initial
 set result=false
 if "%1" == "-h" set result=true
 if "%1" == "--help" set result=true
 if "%result%" == "true" (
 goto :usage
 )
 if "%1" == "-b" set result=true
 if "%1" == "--board" set result=true
 if "%result%" == "true" (
 goto :board_list
 )
 if "%1" == "-n" set result=true
 if "%1" == "--no-lib" set result=true
 if "%result%" == "true" (
 set LIB_INSTALL=false
 shift & goto :initial
 )
 if "%1" == "-c" set result=true
 if "%1" == "--cflag" set result=true
 if "%result%" == "true" (
 set CFLAGS=%2
 if not defined CFLAGS (
 echo %PROGRAM%: option requires an argument -- 'c'
 goto :try_usage
 )
 shift & shift & goto :initial
 )
 if "%1" == "-p" set result=true
 if "%1" == "--prefix" set result=true
 if "%result%" == "true" (
 set PREFIX=%2
 if not defined PREFIX (
 echo %PROGRAM%: option requires an argument -- 'p'
 goto :try_usage
 )
 shift & shift & goto :initial
 )

:: handle non-option arguments
set BOARD=%1
set WORKSPACE=%2

goto :eof


:: Help section

:usage
echo Usage: %PROGRAM% [OPTIONS]... BOARD... WORKSPACE
echo Install BOARD to WORKSPACE location.
echo WORKSPACE directory doesn't already exist!
echo.
echo Mandatory arguments to long options are mandatory for short options too.
echo   -h, --help                   display this help and exit
echo   -b, --boards                 inquire about available CS3 boards
echo   -c, --cflag=CFLAGS           making the CS3 BOARD libraries for CFLAGS
echo   -p. --prefix=PREFIX          install CS3 BOARD libraries in PREFIX
echo                                [lib]
echo   -n, --no-lib                 don't install CS3 BOARD libraries by default
goto :eof

:try_usage
echo Try '%PROGRAM% --help' for more information
goto :eof
Solveur d'équations
la source
1

Voici l'analyseur d'arguments. Vous pouvez mélanger tous les arguments de chaîne (conservés intacts) ou les options échappées (simples ou paires option / valeur). Pour le tester, décommentez les 2 dernières instructions et exécutez-le comme:

getargs anystr1 anystr2 /test$1 /test$2=123 /test$3 str anystr3

Le caractère d'échappement est défini comme "_SEP_=/", redéfinir si nécessaire.

@echo off

REM Command line argument parser. Format (both "=" and "space" separators are supported):
REM   anystring1 anystring2 /param1 /param2=value2 /param3 value3 [...] anystring3 anystring4
REM Returns enviroment variables as:
REM   param1=1
REM   param2=value2
REM   param3=value3
REM Leading and traling strings are preserved as %1, %2, %3 ... %9 parameters
REM but maximum total number of strings is 9 and max number of leading strings is 8
REM Number of parameters is not limited!

set _CNT_=1
set _SEP_=/

:PARSE

if %_CNT_%==1 set _PARAM1_=%1 & set _PARAM2_=%2
if %_CNT_%==2 set _PARAM1_=%2 & set _PARAM2_=%3
if %_CNT_%==3 set _PARAM1_=%3 & set _PARAM2_=%4
if %_CNT_%==4 set _PARAM1_=%4 & set _PARAM2_=%5
if %_CNT_%==5 set _PARAM1_=%5 & set _PARAM2_=%6
if %_CNT_%==6 set _PARAM1_=%6 & set _PARAM2_=%7
if %_CNT_%==7 set _PARAM1_=%7 & set _PARAM2_=%8
if %_CNT_%==8 set _PARAM1_=%8 & set _PARAM2_=%9

if "%_PARAM2_%"=="" set _PARAM2_=1

if "%_PARAM1_:~0,1%"=="%_SEP_%" (
  if "%_PARAM2_:~0,1%"=="%_SEP_%" (
    set %_PARAM1_:~1,-1%=1
    shift /%_CNT_%
  ) else (
    set %_PARAM1_:~1,-1%=%_PARAM2_%
    shift /%_CNT_%
    shift /%_CNT_%
  )
) else (
  set /a _CNT_+=1
)

if /i %_CNT_% LSS 9 goto :PARSE

set _PARAM1_=
set _PARAM2_=
set _CNT_=

rem getargs anystr1 anystr2 /test$1 /test$2=123 /test$3 str anystr3
rem set | find "test$"
rem echo %1 %2 %3 %4 %5 %6 %7 %8 %9

:EXIT
oscar
la source