Синтаксичний розбір аргументу файлу Windows Bat


84

Мені потрібен мій файл bat, щоб прийняти кілька необов’язкових іменованих аргументів.

mycmd.bat man1 man2 -username alice -otheroption

Наприклад, моя команда має 2 обов’язкові параметри та два необов’язкові параметри (-користувач), що має значення аргументу alice та -otheroption:

Я хотів би мати можливість збити ці значення у змінні.

Просто дзвоніть комусь, хто вже вирішив це. Людина, ці файли кажанів - це біль.


12
Будь-яка причина, чому вам доводиться робити це у файлі BAT, а не говорити у VBScript? Можливо, є спосіб зробити це у файлі BAT, але, дотримуючись підходу до файлу BAT, ти "ти потрапляєш у світ болю, синку". :-)
Алек Девіс,

Ще краще використовувати PowerShell. Він має дуже вдосконалене та вбудоване управління параметрами.
Bill_Stewart

1
@AlekDavis, ти, мабуть, маєш рацію, але я дуже VBScript-фобічний. Якщо мені коли-небудь довелося використовувати VBScript, я думаю, що я вже відчував би біль. Я із задоволенням напишу собі пакетний файл, щоб щось автоматизувати, тоді, можливо, якщо я хочу зробити це більш корисним для людей, тоді додам кілька аргументів. Часто деякі з них є необов’язковими. Жодного разу, коли я покинув світ банківської справи, я не думав, або хтось припускав, що "для цього вам знадобиться VBScript".
icc97

@chickeninabiscuit: Посилання більше не працює. Версія машини зворотного зв'язку робить. Я не знаю , якщо я змінити свій коментар , якщо він зміниться, щоб виглядати , як я зробив роботу чи ні я просто хочу , щоб вставити його тут замість: http://web.archive.org/web/20090403050231/http://www.pcguide.com/vb/showthread.php?t=52323.
Брент Ріттенхаус,

Відповіді:


112

Хоча я, як правило, погоджуюся з коментарем @AlekDavis , тим не менше існує кілька способів зробити це в оболонці NT.

Підхід, яким я б скористався командою SHIFT та IF умовного розгалуження, приблизно такий ...

@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 /2зміщення, починаючи з аргументу 2. Він не зміщується два рази. Чи не слід його замінити SHIFT & SHIFT?
dbenham

2
Добре - я розумію, чому код зазвичай працює - SHIFT у циклі в кінцевому підсумку переміщує параметри в аргумент 1, і шкоди не завдається (зазвичай). Але початковий SHIFT / 2, безумовно, є помилкою, яка пошкодить результати, якщо значення arg 1 відповідає одному з імен опцій. Спробуйте бігти mycmd.bat -username anyvalue -username myName -otheroption othervalue. Результатом має бути user = myName, other = othervalue; але код надає user = -username, other = othervalue. Виправлено помилку, замінивши SHIFT /2на SHIFT & SHIFT.
dbenham

2
@Christian - ewall неправильний ;- його не можна використовувати замість &.
dbenham

1
Це чудовий початок, але я бачу 3 проблеми. Перший: а) Після рядка 10, %1і %2вже був "Використано". б) Сингл SHIFTу рядку 11 зміщує значення на %21 позицію вниз і потім стає доступним (знову ж) як %1, а значення "Невикористане" %3- як %2. в) IFРядок 13 бачить це вже "Використовуване" значення, з %2якого зараз %1. г) Якщо "ім'я користувача" з рядка 10 виглядає так "-otheroption", воно буде використано знову і otherбуде встановлено нове значення в %2, що, ймовірно, було назвою наступного варіанту. --->
Кевін Феган

2
Друга проблема: У запитанні до ОП значення -otheroptionне супроводжувалось значенням, тому воно, ймовірно, малося на увазі як -Flagваріант типу, і не повинно використовувати 2-й аргумент. 3-е: Якщо аргумент %1не обробляється внутрішніми IFвисловлюваннями, він мовчки ігнорується. Можливо, у коді відображатиметься повідомлення про те, що це неприпустимий варіант. Це можна зробити, додавши необхідні SHIFTта GOTO :loopвисловлювання до внутрішніх IFвиписок, або додавши ELSEзаяву.
Kevin Fegan

74

Вибрана відповідь працює, але вона може використати певні покращення.

  • Можливо, параметри слід ініціалізувати до значень за замовчуванням.
  • Було б непогано зберегти% 0, а також необхідні аргументи% 1 та% 2.
  • Наявно болісно мати блок IF для кожного варіанту, особливо зі збільшенням кількості варіантів.
  • Було б непогано мати простий і стислий спосіб швидкого визначення всіх параметрів та стандартних налаштувань в одному місці.
  • Було б непогано підтримувати автономні опції, які слугують прапорами (значення не слід після опції).
  • Ми не знаємо, чи аргумент укладено в лапки. Також ми не знаємо, чи було передано значення аргументу з використанням екранованих символів. Краще отримати доступ до аргументу за допомогою% ~ 1 і вкласти призначення у лапки. Тоді пакет може розраховувати на відсутність огороджувальних лапок, але спеціальні символи все ще безпечні, не виходячи з екрана. (Це не кульковий доказ, але він обробляє більшість ситуацій)

Моє рішення покладається на створення змінної OPTIONS, яка визначає всі параметри та значення за замовчуванням. ОПЦІЇ також використовуються для перевірки того, чи дійсна надана опція. Величезна кількість коду зберігається шляхом простого зберігання значень опцій у змінних, названих такими самими, як опція. Кількість коду є постійною, незалежно від того, скільки параметрів визначено; має змінитися лише визначення ВАРІАНТІВ.

EDIT - Крім того, код циклу: повинен змінюватися, якщо кількість обов'язкових позиційних аргументів змінюється. Наприклад, часто всі аргументи називаються, і в цьому випадку потрібно проаналізувати аргументи, що починаються з позиції 1 замість 3. Отже, у циклі: всі 3 стають 1, а 4 стають 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!

Коду насправді не так багато. Більшість коду вище - це коментарі. Ось точно такий самий код, без коментарів.

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


Це рішення надає аргументи стилю Unix у пакеті Windows. Це не є нормою для Windows - пакет, як правило, має параметри, що передують необхідним аргументам, і параметри мають префікс /.

Методи, що використовуються у цьому рішенні, легко адаптуються до параметрів у стилі Windows.

  • Цикл розбору завжди шукає варіант на %1, і він триває до тих пір, поки аргумент 1 не почнеться з/
  • Зверніть увагу, що призначення SET слід укладати в лапки, якщо назва починається з /.
    SET /VAR=VALUEне
    SET "/VAR=VALUE"працює. Я все одно роблю це у своєму рішенні.
  • Стандартний стиль Windows виключає можливість першого необхідного значення аргументу, починаючи з /. Це обмеження можна усунути, використовуючи неявно визначену //опцію, яка служить сигналом для виходу з циклу розбору опцій. Нічого не буде збережено для //"опції".


Оновлення 2015-12-28: Підтримка !значень опцій

У наведеному вище коді кожен аргумент розгортається, у той час як увімкнено відкладене розширення, а це означає, що !, швидше за все, розібрано, або ще щось подібне !var!розширено. Крім того, ^їх також можна позбавити, якщо вони !є. Наступні невеликі зміни в ун-коментував коди знімає обмеження таких , що !і ^зберігаються в значеннях опцій.

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

Дякую за дуже вишукане рішення, мені це подобається більше, ніж прийняте як відповідь. Єдине, що ви пропустили, це сказати, як використовувати названі параметри в пакетному сценарії, наприклад echo% -username%
Roboblob

1
Дуже елегантне рішення. Якщо ви розглядаєте можливість використовувати це, майте на увазі, що наданий код почне розбиратися в аргументі 3. Це не те, що ви хочете в загальному випадку. Замініть "3" на "1", щоб це виправити. @dbenham було б непогано, якби ви могли зробити це більш очевидним у оригінальній публікації, я, мабуть, не єдина, кого спочатку спантеличила.
окрокет

@ocroquette - Гарна думка. У мене не було великого вибору, враховуючи те, що я повинен був відповісти на конкретне питання. Але код потрібно модифікувати, коли змінюється кількість обов’язкових позиційних аргументів. Я спробую відредагувати свою відповідь, щоб вона стала зрозумілою.
dbenham

Мені це подобається. Дякуємо за рішення, воно все ще працює в 2019 році досить чисто для деяких сценаріїв IDE!
Роберт Сміт,

18

Якщо ви хочете використовувати необов’язкові аргументи, але не іменовані, тоді цей підхід спрацював у мене. Я думаю, що це набагато простіший код.

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"==""не вдасться, якщо аргумент містить самі лапки. Просто використовуйте будь-який інший неспеціальний символ замість цього, наприклад ._ = [] # etc
Fr0sT

2
Я не думаю, що це працює, якщо користувач не знає використовувати "" замість аргументу. В іншому випадку, як я можу коли-небудь встановити ім'я БД, але залишити сервер БД порожнім?
Шон Лонг

Як інакше ви бачите, що інша відповідь досягає цього? @SeanLong
sehe

@SeanLong Правильно, тому користувач повинен виконувати наказ. Отже, слід поставити найбільш необхідні аргументи на перші місця.
Мартін Браун,

Це, безумовно, був найпростіший варіант для мене. Дякуємо за публікацію :)
Стів Бауман,

2

Створення динамічних змінних

введіть тут опис зображення

Плюси

  • Працює для> 9 аргументів
  • Тримає %1, %2... %*в такті
  • Роботи для обох /argі -argстилю
  • Немає попередніх знань про аргументи
  • Реалізація окрема від основної процедури

Мінуси

  • Старі аргументи можуть потрапляти в послідовний цикл, тому використовуйте setlocalдля локального обсягу або напишіть супровідну :CLEAR-ARGSпроцедуру!
  • Немає псевдонім підтримки ще (наприклад , --forceв -f)
  • Немає порожніх ""аргументів

Використання

Ось приклад, як такі аргументи відносяться до змінних .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

Впровадження

@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


Ви також можете перемістити вміст :ARG-PARSERпрограми до зовнішнього файлу .bat, наприклад, arg-parser.batщоб зробити його доступним і для інших сценаріїв.
Саймон Штрайхер

1

Одного разу я написав програму, яка обробляє короткі (-h), long (--help) та неопційні аргументи в командному файлі. Ця техніка включає:

  • аргументи без опції, за якими слідують аргументи опції.

  • оператор зсуву для тих опцій, які не мають аргументів, таких як '--help'.

  • два оператори часового зсуву для тих опцій, для яких потрібен аргумент.

  • цикл через мітку для обробки всіх аргументів командного рядка.

  • Вийдіть зі сценарію та зупиніть обробку для тих опцій, які не потребують подальших дій, таких як '--help'.

  • Написав довідкові функції для керівництва користувача

Ось мій код.

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

Ось аналізатор аргументів. Ви можете змішати будь-які рядкові аргументи (не зворушені) або екрановані параметри (одиночні або пари варіант / значення). Щоб протестувати, розкоментуйте останні 2 оператори та запустіть як:

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

Втеча char визначається як "_SEP_=/", за необхідності перевизначте.

@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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.