95 votes

Analyse des arguments facultatifs du fichier Bat de Windows

J'ai besoin que mon fichier bat accepte plusieurs arguments optionnels nommés.

mycmd.bat man1 man2 -username alice -otheroption

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

J'aimerais pouvoir transformer ces valeurs en variables.

Je lance un appel à tous ceux qui ont déjà résolu ce problème. Ces fichiers bat sont une douleur.

14 votes

Y a-t-il une raison pour laquelle vous devez faire cela dans un fichier BAT et non en VBScript ? Il y a peut-être un moyen de le faire dans un fichier BAT, mais si vous vous en tenez à cette approche, vous "entrez dans un monde de douleur, fiston" :-)

0 votes

Le mieux est encore d'utiliser PowerShell. Il dispose d'une gestion des paramètres très avancée et intégrée.

2 votes

@AlekDavis, vous avez probablement raison, mais je suis extrêmement phobique du VBScript. Si je me retrouvais un jour à devoir utiliser VBScript, je pense que je serais déjà dans un monde de douleur. Je me contenterai d'écrire un fichier batch pour automatiser quelque chose, et si je veux le rendre plus utile aux gens, j'ajouterai des arguments. Souvent, certains de ces arguments sont facultatifs. Depuis que j'ai quitté le monde de la banque, je n'ai jamais pensé, ou quelqu'un m'a suggéré, que je devais utiliser du VBScript pour cela.

130voto

ewall Points 10676

Bien que je sois d'accord avec Commentaire de @AlekDavis il existe néanmoins plusieurs façons de le faire dans le shell NT.

L'approche que je mettrais à profit pour SHIFT et SI un branchement conditionnel, 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

1 votes

SHIFT /2 Il ne change pas deux fois de vitesse. Ne devrait-elle pas être remplacée par SHIFT & SHIFT ?

2 votes

OK - Je vois pourquoi le code fonctionne habituellement - le SHIFT dans la boucle déplace finalement les options vers l'argument 1 et aucun mal n'est fait (habituellement). Mais le SHIFT /2 initial est certainement un bug qui corrompt les résultats si la valeur de l'arg 1 correspond à l'un des noms d'options. Essayez d'exécuter mycmd.bat -username anyvalue -username myName -otheroption othervalue . Le résultat devrait être user=myName, other=othervalue ; mais le code donne user=-username, other=othervalue. Le bogue est corrigé en remplaçant SHIFT /2 con SHIFT & SHIFT .

0 votes

@ewall Je suis surpris qu'un tel système puisse être réalisé avec si peu de code. Je n'ai qu'une seule question cependant, pourquoi avez-vous besoin de SHIFT & SHIFT ?

80voto

dbenham Points 46458

La réponse sélectionnée fonctionne, mais elle pourrait être améliorée.

  • Les options devraient probablement être initialisées à des valeurs par défaut.
  • Il serait bon de préserver %0 ainsi que les args %1 et %2 requis.
  • Il devient pénible d'avoir un bloc IF pour chaque option, surtout lorsque le nombre d'options augmente.
  • Il serait agréable de disposer d'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 supporter des options autonomes qui servent de drapeaux (aucune valeur ne suit l'option).
  • On ne sait pas si un arg est entre guillemets. Nous ne savons pas non plus si une valeur arg a été passée en utilisant des caractères échappés. Il est préférable d'accéder à un arg en utilisant %~1 et de mettre l'affectation entre guillemets. Le batch peut alors compter sur l'absence de guillemets, mais les caractères spéciaux restent généralement sûrs sans échappement. (Cette méthode n'est pas infaillible, mais elle permet de gérer 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ée pour tester si une option fournie est valide. Une quantité considérable de code est économisée en stockant simplement les valeurs des options dans des variables portant le même nom que l'option. La quantité de code est constante quel que soit le nombre d'options définies ; seule la définition de OPTIONS doit être modifiée.

EDITAR - En outre, le code de la :loop doit être modifié si le nombre d'arguments positionnels obligatoires change. Par exemple, il arrive souvent que tous les arguments soient nommés, auquel cas vous souhaitez analyser les arguments commençant à la position 1 au lieu de 3. Ainsi, dans la :loop, les 3 deviennent 1 et les 4 deviennent 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 pas vraiment beaucoup de code. La plupart du code ci-dessus est constitué de 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 - le batch a généralement les options précédant les arguments requis et les options sont préfixées par / .

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

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

Mise à jour 2015-12-28 : Soutien aux ! dans les valeurs des options

Dans le code ci-dessus, chaque argument est développé pendant que l'expansion retardée est activée, ce qui signifie que ! sont très probablement dépouillés, ou alors quelque chose comme !var! est étendu. En outre, ^ peut également être dépouillé si ! est présent. La petite modification suivante apportée au code non commenté supprime la limitation de telle sorte que ! y ^ sont préservés dans les valeurs des options.

@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!

0 votes

Merci pour cette solution très élégante, je l'aime plus que celle acceptée comme réponse. La seule chose que vous avez oubliée est de dire comment utiliser les paramètres nommés dans le lot script, par exemple echo %-username%.

3 votes

Solution très élégante. Si vous envisagez de l'utiliser, sachez que le code tel qu'il est 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 des "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 à avoir été confus au début.

0 votes

@ocroquette - Bon point. Je n'avais pas vraiment le choix, étant donné que je devais répondre à la question spécifique. Mais le code doit être modifié lorsque le nombre d'arguments positionnels obligatoires change. Je vais essayer de modifier ma réponse pour que ce soit plus clair.

21voto

charlie.mott Points 61

Si vous voulez utiliser des arguments facultatifs, mais pas des arguments nommés, alors cette approche a fonctionné pour moi. Je pense que ce code est 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%

0 votes

IF "%1"=="" échouera si l'argument contient lui-même des guillemets. Utilisez simplement tout autre symbole non spécial à la place, comme ._=[]#, etc.

2 votes

Je ne pense pas que cela fonctionne, sauf si l'utilisateur sait 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 ?

0 votes

Comment une autre réponse permet-elle d'atteindre cet objectif ? @SeanLong

5voto

Simon Streicher Points 372

Création de variables dynamiques

enter image description here

Pour

  • Fonctionne pour >9 arguments
  • Gardez %1 , %2 , ... %* dans le tact
  • Fonctionne pour les deux /arg y -arg style
  • Aucune connaissance préalable des arguments
  • L'implémentation est séparée de la routine principale

Cons

  • Les anciens arguments peuvent s'infiltrer dans les exécutions consécutives, utilisez donc setlocal pour le cadrage local ou rédiger un document d'accompagnement :CLEAR-ARGS la routine !
  • Pas encore de support pour les alias (comme --force a -f )
  • Pas de vide "" soutien des arguments

Utilisation

Voici un exemple de la manière dont les arguments suivants se rapportent aux 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

Mise en œuvre

@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_D%

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

2voto

Equation Solver Points 481

J'ai écrit un programme qui gère les arguments courts (-h), longs (--help) et sans option dans un fichier batch. Cette technique comprend :

  • des arguments de non-option suivis d'un argument d'option.

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

  • opérateur à deux temps pour les options qui nécessitent un argument.

  • Boucle dans une étiquette pour traiter tous les arguments de la ligne de commande.

  • Quitter script et arrêter le traitement pour les options qui n'ont pas besoin de nécessiter une action supplémentaire comme '--help'.

  • Rédaction de fonctions d'aide pour guider les utilisateurs

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X