Optionale Argumentanalyse für Windows Bat-Dateien

83

Ich benötige meine bat-Datei, um mehrere optionale benannte Argumente zu akzeptieren.

mycmd.bat man1 man2 -username alice -otheroption

Zum Beispiel hat mein Befehl zwei obligatorische Parameter und zwei optionale Parameter (-username) mit dem Argumentwert alice und -otheroption:

Ich möchte diese Werte in Variablen umwandeln können.

Rufen Sie einfach jemanden an, der dies bereits gelöst hat. Mann, diese Fledermausfeilen sind ein Schmerz.

Hühnchen-Keks
quelle
12
Gibt es einen Grund, warum Sie dies in einer BAT-Datei tun müssen und nicht in VBScript? Es mag eine Möglichkeit geben, dies in einer BAT-Datei zu tun, aber wenn Sie sich an den Ansatz der BAT-Datei halten, "betreten Sie eine Welt voller Schmerzen, mein Sohn". :-)
Alek Davis
Noch besser ist die Verwendung von PowerShell. Es verfügt über eine sehr fortschrittliche und integrierte Parameterverwaltung.
Bill_Stewart
1
@AlekDavis, Sie haben wahrscheinlich Recht, aber ich bin sehr VBScript-phobisch. Wenn ich jemals VBScript verwenden müsste, wäre ich wahrscheinlich schon in einer Welt voller Schmerzen. Ich schreibe mir gerne eine Batch-Datei, um etwas zu automatisieren. Wenn ich sie dann für die Leute nützlicher machen möchte, füge ich einige Argumente hinzu. Oft sind einige davon optional. Zu keinem Zeitpunkt, seit ich die Welt des Bankwesens verlassen habe, habe ich gedacht oder jemand hat vorgeschlagen, "dafür wollen Sie etwas VBScript".
icc97
@chickeninabiscuit: Link funktioniert nicht mehr. Wayback-Maschinenversion tut. Ich weiß nicht, ob ich Ihren Kommentar bearbeite, ob er sich so ändert, als hätte ich die Arbeit erledigt oder nicht. Ich werde ihn stattdessen hier einfügen : http://web.archive.org/web/20090403050231/http://www.pcguide.com/vb/showthread.php?t=52323.
Brent Rittenhouse

Antworten:

111

Obwohl ich eher damit einverstanden bin Kommentar von @AlekDavis , gibt es in der NT-Shell mehrere Möglichkeiten, dies zu tun.

Der Ansatz, den ich mit dem Befehl SHIFT und der bedingten Verzweigung von IF nutzen würde , so etwas in der Art ...

@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
quelle
1
SHIFT /2Verschiebungen ab Argument 2. Es wird nicht zweimal verschoben. Sollte es nicht durch ersetzt werden SHIFT & SHIFT?
Dbenham
2
OK - ich verstehe, warum der Code normalerweise funktioniert - die UMSCHALTTASTE in der Schleife verschiebt die Optionen schließlich auf Argument 1 und es wird (normalerweise) kein Schaden angerichtet. Aber der anfängliche SHIFT / 2 ist definitiv ein Fehler, der die Ergebnisse verfälscht, wenn der Wert von arg 1 zufällig mit einem der Optionsnamen übereinstimmt. Versuche zu rennen mycmd.bat -username anyvalue -username myName -otheroption othervalue. Das Ergebnis sollte user = myName, other = othervalue sein; Der Code gibt jedoch user = -username, other = othervalue an. Der Fehler wird durch Ersetzen fixiert SHIFT /2mit SHIFT & SHIFT.
Dbenham
2
@Christian - ewall ist falsch ;- es kann nicht anstelle von verwendet werden &.
Dbenham
1
Dies ist ein guter Anfang, aber ich sehe 3 Probleme. Erstens: a) Nach Zeile 10 %1und %2wurden "verwendet". b) Die Single SHIFTin Zeile 11 verschiebt den Wert von %2um 1 Position nach unten und ist dann (wieder) als verfügbar %1, und der Wert "Nicht verwendet" von %3ist als verfügbar %2. c) In IFZeile 13 wird dieser bereits "Verwendete" Wert angezeigt, von %2dem jetzt in %1. d) Wenn der "Benutzername" aus Zeile 10 so aussieht "-otheroption", wird er erneut verwendet und otherauf den neuen Wert in gesetzt %2, der wahrscheinlich der Name der nächsten Option war. --->
Kevin Fegan
2
Das zweite Problem: In der OP-Frage wurde dem -otheroptionkein Wert gefolgt, daher war er wahrscheinlich als Typoption gedacht -Flagund sollte kein zweites Argument verbrauchen. 3. Wenn das Argument %1nicht von den internen IFAnweisungen behandelt wird, wird es stillschweigend ignoriert. Der Code sollte wahrscheinlich eine Meldung anzeigen, die angibt, dass es sich nicht um eine gültige Option handelt. Dies kann durch Hinzufügen der erforderlichen Anweisungen SHIFTund GOTO :loopAnweisungen zu den internen IFAnweisungen oder durch Hinzufügen einer ELSEAnweisung erfolgen.
Kevin Fegan
74

Die ausgewählte Antwort funktioniert, könnte aber verbessert werden.

  • Die Optionen sollten wahrscheinlich auf Standardwerte initialisiert werden.
  • Es wäre schön,% 0 sowie die erforderlichen Argumente% 1 und% 2 beizubehalten.
  • Es wird schwierig, für jede Option einen IF-Block zu haben, insbesondere wenn die Anzahl der Optionen zunimmt.
  • Es wäre schön, eine einfache und präzise Möglichkeit zu haben, alle Optionen und Standardeinstellungen schnell an einem Ort zu definieren.
  • Es wäre gut, eigenständige Optionen zu unterstützen, die als Flags dienen (kein Wert nach der Option).
  • Wir wissen nicht, ob ein Argument in Anführungszeichen steht. Wir wissen auch nicht, ob ein arg-Wert mit maskierten Zeichen übergeben wurde. Es ist besser, mit% ~ 1 auf ein Argument zuzugreifen und die Zuweisung in Anführungszeichen zu setzen. Dann kann sich der Stapel auf das Fehlen von Anführungszeichen verlassen, aber Sonderzeichen sind im Allgemeinen immer noch sicher, ohne zu entkommen. (Dies ist nicht kugelsicher, aber es behandelt die meisten Situationen)

Meine Lösung basiert auf der Erstellung einer OPTIONS-Variablen, die alle Optionen und ihre Standardeinstellungen definiert. OPTIONEN wird auch verwendet, um zu testen, ob eine bereitgestellte Option gültig ist. Eine enorme Menge an Code wird gespeichert, indem die Optionswerte einfach in Variablen gespeichert werden, die den gleichen Namen wie die Option haben. Die Codemenge ist unabhängig von der Anzahl der definierten Optionen konstant. Nur die OPTIONS-Definition muss geändert werden.

BEARBEITEN - Außerdem muss sich der: -Schleifencode ändern, wenn sich die Anzahl der obligatorischen Positionsargumente ändert. Beispielsweise werden häufig alle Argumente benannt. In diesem Fall möchten Sie Argumente ab Position 1 anstelle von 3 analysieren. In der: -Schleife werden also alle 3 zu 1 und 4 zu 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!

Es gibt wirklich nicht so viel Code. Der größte Teil des obigen Codes besteht aus Kommentaren. Hier ist genau der gleiche Code ohne die Kommentare.

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


Diese Lösung bietet Argumente im Unix-Stil innerhalb eines Windows-Stapels. Dies ist nicht die Norm für Windows - Batch hat normalerweise die Optionen vor den erforderlichen Argumenten und den Optionen wird ein Präfix vorangestellt/ .

Die in dieser Lösung verwendeten Techniken lassen sich leicht an einen Windows-Optionsstil anpassen.

  • Die Parsing-Schleife sucht immer nach einer Option bei %1 und wird fortgesetzt, bis arg 1 nicht mit beginnt/
  • Beachten Sie, dass SET-Zuweisungen in Anführungszeichen gesetzt werden müssen , wenn der Name mit beginnt /.
    SET /VAR=VALUEschlägt fehl
    SET "/VAR=VALUE" funktioniert. Ich mache das sowieso schon in meiner Lösung.
  • Der Standard-Windows-Stil schließt die Möglichkeit aus, dass der erste erforderliche Argumentwert mit beginnt /. Diese Einschränkung kann beseitigt werden, indem eine implizit definierte //Option verwendet wird, die als Signal zum Verlassen der Optionsanalyse-Schleife dient. Für die //"Option" wird nichts gespeichert .


Update 28.12.2015: Unterstützung für !In-Optionswerte

Im obigen Code wird jedes Argument erweitert, während die verzögerte Erweiterung aktiviert ist. Dies bedeutet, dass !es höchstwahrscheinlich entfernt wird oder dass etwas Ähnliches !var!erweitert wird. Darüber hinaus ^kann auch abgestreift werden, wenn !vorhanden. Die folgende kleine Änderung am nicht kommentierten Code hebt die Einschränkung auf, sodass !und ^in Optionswerten beibehalten werden.

@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
quelle
Vielen Dank für die sehr ellegante Lösung, ich mag dies mehr als die als Antwort akzeptierte. Das einzige, was Sie verpasst haben, ist zu sagen, wie die benannten Parameter im Batch-Skript verwendet werden sollen, zum Beispiel echo% -username%
Roboblob
1
Sehr elegante Lösung. Wenn Sie dies in Betracht ziehen, beachten Sie, dass der bereitgestellte Code bei Argument 3 zu analysieren beginnt. Dies ist im allgemeinen Fall nicht das, was Sie wollen. Ersetzen Sie die "3" durch "1", um dies zu beheben. @dbenham es wäre schön, wenn du dies im ursprünglichen Beitrag deutlicher machen könntest, ich bin wahrscheinlich nicht der einzige, der zuerst verwirrt war.
Okroquette
@ocroquette - Guter Punkt. Ich hatte nicht viel Auswahl, da ich die spezifische Frage beantworten musste. Der Code muss jedoch geändert werden, wenn sich die Anzahl der obligatorischen Positionsargumente ändert. Ich werde versuchen, meine Antwort zu bearbeiten, um es klar zu machen.
Dbenham
Ich liebe es. Vielen Dank für die Lösung, sie funktioniert auch 2019 noch recht sauber für einige IDE-Skripte!
Robert Smith
18

Wenn Sie optionale Argumente, aber keine benannten Argumente verwenden möchten, hat dieser Ansatz für mich funktioniert. Ich denke, dies ist viel einfacher zu befolgen.

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
quelle
IF "%1"==""schlägt fehl, wenn das Argument selbst Anführungszeichen enthält. Verwenden Sie stattdessen einfach ein anderes nicht spezielles Symbol, z. B. ._ = [] # etc
Fr0sT
2
Ich denke nicht, dass dies funktioniert, wenn der Benutzer nicht weiß, "" anstelle eines Arguments zu verwenden. Wie kann ich sonst jemals einen DB-Namen festlegen, aber den DB-Server leer lassen?
Sean Long
Wie sonst sehen Sie eine andere Antwort, die das erreicht? @ SeanLong
sehe
@ SeanLong Richtig, daher muss der Benutzer die Anweisung befolgen. Sie sollten also die am meisten erforderlichen Argumente an erster Stelle setzen.
Martin Braun
Dies war definitiv die einfachste Option für mich. Danke fürs posten :)
Steve Bauman
1

Einmal hatte ich ein Programm geschrieben, das die Argumente short (-h), long (--help) und non-option in der Batch-Datei verarbeitet. Diese Techniken umfassen:

  • Nicht-Optionsargumente, gefolgt von Optionsargumenten.

  • Shift-Operator für Optionen ohne Argument wie '--help'.

  • Zwei-Zeit-Shift-Operator für die Optionen, für die ein Argument erforderlich ist.

  • Durchlaufen Sie eine Beschriftung, um alle Befehlszeilenargumente zu verarbeiten.

  • Beenden Sie das Skript und beenden Sie die Verarbeitung für die Optionen, für die keine weiteren Aktionen erforderlich sind, z. B. '--help'.

  • Schrieb Hilfefunktionen für die Benutzerführung

Hier ist mein 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
Gleichungslöser
quelle
1

Hier ist der Argumentparser. Sie können beliebige Zeichenfolgenargumente (unberührt) oder maskierte Optionen (Einzel- oder Options- / Wertepaare) mischen. Um es zu testen, kommentieren Sie die letzten 2 Anweisungen aus und führen Sie sie aus als:

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

Escape char ist definiert als "_SEP_=/", bei Bedarf neu definieren.

@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
quelle
1

Erstellung dynamischer Variablen

Geben Sie hier die Bildbeschreibung ein

Vorteile

  • Funktioniert für> 9 Argumente
  • Hält %1, %2...%* in Takt
  • Funktioniert für beide /argund-arg Stil
  • Keine Vorkenntnisse zu Argumenten
  • Die Implementierung ist von der Hauptroutine getrennt

Nachteile

  • Alte Argumente können in aufeinanderfolgende Läufe gelangen. Verwenden Sie sie daher setlocalfür das lokale Scoping oder schreiben Sie eine Begleitung:CLEAR-ARGS Routine!
  • Kein Alias - Unterstützung noch nicht (wie --forcezum-f )
  • Keine leere ""Argumentunterstützung

Verwendung

Hier ist ein Beispiel, wie sich die folgenden Argumente auf .bat-Variablen beziehen:

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

Implementierung

@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
quelle
Sie können den Inhalt der :ARG-PARSERRoutine auch in eine externe .bat-Datei verschieben arg-parser.bat, um sie auch anderen Skripten zur Verfügung zu stellen.
Simon Streicher