Analisi dell'argomento facoltativo del file Bat di Windows


84

Ho bisogno del mio file bat per accettare più argomenti con nome facoltativi.

mycmd.bat man1 man2 -username alice -otheroption

Ad esempio il mio comando ha 2 parametri obbligatori e due parametri opzionali (-username) che ha un valore di argomento di alice e -otheroption:

Mi piacerebbe essere in grado di cogliere questi valori in variabili.

Sto solo facendo una chiamata a qualcuno che ha già risolto il problema. Amico, questi file di pipistrelli sono un dolore.


12
Qualche motivo per cui devi farlo in un file BAT e non dire in VBScript? Potrebbe esserci un modo per farlo in un file BAT, ma attenendosi all'approccio del file BAT, "stai entrando in un mondo di dolore, figliolo". :-)
Alek Davis

Ancora meglio è usare PowerShell. Ha una gestione dei parametri molto avanzata e integrata.
Bill_Stewart

2
@ AlekDavis, probabilmente hai ragione, ma io sono molto fobico di VBScript. Se mai mi dovessi trovare a dover usare VBScript, penso che sarei già in un mondo di dolore. Scriverò felicemente un file batch per automatizzare qualcosa, quindi forse se voglio renderlo più utile per le persone, aggiungerò alcuni argomenti. Spesso alcuni di questi sono opzionali. Da quando ho lasciato il mondo bancario non ho mai pensato, o qualcuno mi ha suggerito "ti servirà un po 'di VBScript per quello".
icc97

@chickeninabiscuit: Link non funziona più. La versione della macchina Wayback lo fa. Non so se modifico il tuo commento se cambierà a guardare come ho fatto il lavoro o no Sto solo andando a incollarlo qui invece: http://web.archive.org/web/20090403050231/http://www.pcguide.com/vb/showthread.php?t=52323.
Brent Rittenhouse

Risposte:


113

Anche se tendo ad essere d'accordo con il commento di @AlekDavis , ci sono comunque diversi modi per farlo nella shell di NT.

L'approccio trarrebbe vantaggio dal comando SHIFT e dalla ramificazione condizionale IF , qualcosa del genere ...

@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
SHIFT /2cambia a partire da arg 2. Non si sposta due volte. Non dovrebbe essere sostituito con SHIFT & SHIFT?
dbenham

2
OK - Capisco perché il codice di solito funziona: lo SHIFT nel ciclo alla fine sposta le opzioni su arg 1 e non viene fatto alcun danno (di solito). Ma lo SHIFT / 2 iniziale è sicuramente un bug che corromperà i risultati se il valore arg 1 corrisponde a uno dei nomi delle opzioni. Prova a correre mycmd.bat -username anyvalue -username myName -otheroption othervalue. Il risultato dovrebbe essere user = myName, other = othervalue; ma il codice fornisce user = -username, other = othervalue. Il bug viene risolto sostituendo SHIFT /2con SHIFT & SHIFT.
dbenham

2
@Christian - ewall non è corretto su ;- non può essere usato al posto di &.
dbenham

1
Questo è un ottimo inizio, ma vedo 3 problemi. Primo: a) Dopo la riga 10, %1e %2sono stati "Usati". b) Il singolo SHIFTnella riga 11 sposta il valore di %21 posizione in basso ed è quindi disponibile (di nuovo) come %1, e il valore "Non utilizzato" di %3è disponibile come %2. c) La IFriga 13 vede questo valore già "Usato" da %2cui si trova ora %1. d) Se il "nome utente" dalla riga 10 ha questo aspetto "-otheroption", verrà usato di nuovo e otherverrà impostato sul nuovo valore in %2, che probabilmente era il nome dell'opzione successiva. --->
Kevin Fegan

2
Il secondo problema: nella domanda OP, -otheroptionnon era seguito da un valore, quindi probabilmente era inteso come -Flagun'opzione di tipo, e non dovrebbe consumare un secondo argomento. 3 °: se l'argomento %1non è gestito dalle IFistruzioni interne , viene silenziosamente ignorato. Il codice dovrebbe probabilmente visualizzare un messaggio che indica che non era un'opzione valida. Ciò può essere fatto aggiungendo le istruzioni richieste SHIFTe GOTO :loopnelle IFistruzioni interne o aggiungendo ELSEun'istruzione.
Kevin Fegan

75

La risposta selezionata funziona, ma potrebbe essere migliorata.

  • Le opzioni dovrebbero probabilmente essere inizializzate ai valori predefiniti.
  • Sarebbe bello conservare% 0 così come gli argomenti obbligatori% 1 e% 2.
  • Diventa un problema avere un blocco IF per ogni opzione, soprattutto quando il numero di opzioni cresce.
  • Sarebbe bello avere un modo semplice e conciso per definire rapidamente tutte le opzioni e le impostazioni predefinite in un unico posto.
  • Sarebbe utile supportare opzioni autonome che fungono da flag (nessun valore dopo l'opzione).
  • Non sappiamo se un argomento è racchiuso tra virgolette. Né sappiamo se un valore arg è stato passato utilizzando caratteri di escape. È meglio accedere a un argomento utilizzando% ~ 1 e racchiudere l'assegnazione tra virgolette. Quindi il batch può fare affidamento sull'assenza di virgolette racchiuse, ma i caratteri speciali sono ancora generalmente sicuri senza sfuggire. (Questo non è a prova di proiettile, ma gestisce la maggior parte delle situazioni)

La mia soluzione si basa sulla creazione di una variabile OPTIONS che definisce tutte le opzioni e le loro impostazioni predefinite. OPTIONS viene utilizzato anche per verificare se un'opzione fornita è valida. Un'enorme quantità di codice viene salvata semplicemente memorizzando i valori delle opzioni in variabili denominate come l'opzione. La quantità di codice è costante indipendentemente dal numero di opzioni definite; solo la definizione delle OPZIONI deve cambiare.

EDIT - Inoltre, il codice: loop deve cambiare se cambia il numero di argomenti posizionali obbligatori. Ad esempio, spesso viene assegnato un nome a tutti gli argomenti, nel qual caso si desidera analizzare gli argomenti che iniziano alla posizione 1 anziché 3. Quindi all'interno del ciclo:, tutti e 3 diventano 1 e 4 diventa 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!

Non c'è davvero molto codice. La maggior parte del codice sopra è un commento. Ecco lo stesso identico codice, senza i commenti.

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


Questa soluzione fornisce argomenti in stile Unix all'interno di un batch di Windows. Questa non è la norma per Windows: batch di solito ha le opzioni che precedono gli argomenti richiesti e le opzioni hanno il prefisso/ .

Le tecniche utilizzate in questa soluzione possono essere facilmente adattate per uno stile di opzioni Windows.

  • Il ciclo di analisi cerca sempre un'opzione in %1e continua finché arg 1 non inizia con/
  • Notare che le assegnazioni SET devono essere racchiuse tra virgolette se il nome inizia con /.
    SET /VAR=VALUEnon riesce
    SET "/VAR=VALUE" . Lo sto già facendo comunque nella mia soluzione.
  • Lo stile standard di Windows preclude la possibilità che il primo valore dell'argomento richiesto inizi con /. Questa limitazione può essere eliminata impiegando //un'opzione definita implicitamente che funge da segnale per uscire dal ciclo di analisi delle opzioni. Niente verrebbe memorizzato per l ' //"opzione".


Aggiornamento 2015-12-28: supporto per i !valori delle opzioni

Nel codice precedente, ogni argomento viene espanso mentre è abilitata l'espansione ritardata, il che significa che !molto probabilmente vengono rimossi, oppure qualcosa di simile !var!viene espanso. Inoltre, ^può anche essere spogliato se !presente. La seguente piccola modifica al codice non commentato rimuove la limitazione in modo tale che !e ^siano conservati nei valori delle opzioni.

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

Grazie per la soluzione molto elegante, mi piace più di quella accettata come risposta. L'unica cosa che ti sei perso è dire come utilizzare i parametri denominati nello script batch, ad esempio echo% -username%
Roboblob

1
Soluzione molto elegante. Se consideri di usarlo, tieni presente che il codice fornito inizierà ad essere analizzato dall'argomento 3. Questo non è quello che vuoi nel caso generale. Sostituisci "3" con "1" per risolvere il problema. @dbenham sarebbe carino se tu potessi renderlo più ovvio nel post originale, probabilmente non sono l'unico ad essere confuso all'inizio.
ocroquette

@ocroquette - Buon punto. Non avevo molta scelta, dato che dovevo rispondere alla domanda specifica. Ma il codice deve essere modificato per quando cambia il numero di argomenti posizionali obbligatori. Proverò a modificare la mia risposta per renderla più chiara.
dbenham

Amo questo. Grazie per la soluzione, funziona ancora nel 2019 in modo abbastanza pulito per alcuni script IDE!
Robert Smith

18

Se vuoi usare argomenti opzionali, ma non argomenti con nome, allora questo approccio ha funzionato per me. Penso che questo sia un codice molto più semplice da seguire.

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%

IF "%1"==""fallirà se l'argomento contiene le virgolette stesso. Usa semplicemente qualsiasi altro simbolo non speciale, come ._ = [] # ecc.
Fr0sT

2
Non penso che funzioni a meno che l'utente non sappia di usare "" al posto di un argomento. Altrimenti, come posso impostare un nome DB ma lasciare vuoto il server DB?
Sean Long,

In quale altro modo vedi un'altra risposta raggiungere questo obiettivo? @SeanLong
guarda il

@SeanLong Right, quindi l'utente deve obbedire all'ordine. Quindi, dovresti mettere gli argomenti più richiesti al primo posto.
Martin Braun

Questa è stata sicuramente l'opzione più semplice per me. Grazie per la pubblicazione :)
Steve Bauman

3

Creazione di variabili dinamiche

inserisci qui la descrizione dell'immagine

Professionisti

  • Funziona per> 9 argomenti
  • Mantiene %1, %2, ...%* nel tatto
  • Funziona per entrambi /arge per lo -argstile
  • Nessuna conoscenza preliminare degli argomenti
  • L'implementazione è separata dalla routine principale

Contro

  • I vecchi argomenti possono trapelare in esecuzioni consecutive, quindi utilizzare setlocalper lo scoping locale o scrivere una :CLEAR-ARGSroutine di accompagnamento !
  • Nessun supporto alias ancora (come --forcea -f)
  • Nessun ""supporto per argomenti vuoti

Utilizzo

Ecco un esempio di come i seguenti argomenti si riferiscono alle variabili .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

Implementazione

@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


Puoi anche spostare il contenuto della :ARG-PARSERroutine in un file .bat esterno arg-parser.batper renderlo disponibile anche ad altri script.
Simon Streicher

1

Una volta ho scritto un programma che gestisce gli argomenti brevi (-h), lunghi (--help) e non di opzione nel file batch. Questa tecnica include:

  • argomenti non di opzione seguiti da argomenti di opzione.

  • operatore shift per quelle opzioni che non hanno argomenti come '--help'.

  • due operatori di spostamento temporale per quelle opzioni che richiedono un argomento.

  • scorrere un'etichetta per elaborare tutti gli argomenti della riga di comando.

  • Esci dallo script e interrompi l'elaborazione per quelle opzioni che non richiedono ulteriori azioni come '--help'.

  • Ha scritto funzioni di aiuto per guidare l'utente

Ecco il mio codice.

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

1

Ecco il parser degli argomenti. È possibile combinare qualsiasi argomento stringa (mantenuto inalterato) o opzioni con escape (singole o coppie opzione / valore). Per testarlo, rimuovere il commento dalle ultime 2 istruzioni ed eseguire come:

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

Il carattere di escape è definito come "_SEP_=/", ridefinisci se necessario.

@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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.